javascript

exercises

exercises.js⚔
/**
 * Blob and File API Exercises
 *
 * Practice working with binary data and files in JavaScript
 */

// =============================================================================
// Exercise 1: Blob Creation and Reading
// =============================================================================

/**
 * Create a Blob from text and read it back
 *
 * @param {string} text - Text content
 * @returns {Promise<{blob: Blob, content: string, size: number}>}
 */
async function createAndReadBlob(text) {
  // TODO:
  // 1. Create a Blob from the text with type 'text/plain'
  // 2. Read the content back using blob.text()
  // 3. Return object with blob, content, and size

  throw new Error('Not implemented');
}

/**
 * Create a JSON Blob and parse it back
 *
 * @param {Object} data - Data to convert to JSON Blob
 * @returns {Promise<{blob: Blob, parsed: Object}>}
 */
async function createJsonBlob(data) {
  // TODO:
  // 1. Create a JSON Blob with proper MIME type
  // 2. Read and parse the content back
  // 3. Return blob and parsed data

  throw new Error('Not implemented');
}

// Test
async function testExercise1() {
  const result = await createAndReadBlob('Hello, World!');
  console.assert(result.content === 'Hello, World!', 'Content should match');
  console.assert(result.size === 13, 'Size should be 13 bytes');
  console.assert(
    result.blob.type === 'text/plain',
    'Type should be text/plain'
  );

  const jsonResult = await createJsonBlob({ name: 'Test', value: 42 });
  console.assert(jsonResult.parsed.name === 'Test', 'JSON name should match');
  console.assert(jsonResult.parsed.value === 42, 'JSON value should match');

  console.log('Exercise 1 passed!');
}

// =============================================================================
// Exercise 2: FileReader with Progress
// =============================================================================

/**
 * Read a Blob with progress reporting
 *
 * @param {Blob} blob - Blob to read
 * @param {Function} onProgress - Callback with progress percentage
 * @returns {Promise<ArrayBuffer>}
 */
function readWithProgress(blob, onProgress) {
  return new Promise((resolve, reject) => {
    // TODO:
    // 1. Create a FileReader
    // 2. Set up onprogress handler that calls onProgress(percent)
    // 3. Set up onload to resolve with result
    // 4. Set up onerror to reject
    // 5. Read as ArrayBuffer

    reject(new Error('Not implemented'));
  });
}

// Test
async function testExercise2() {
  const largeBlob = new Blob([new ArrayBuffer(1024 * 100)]); // 100KB
  const progressEvents = [];

  await readWithProgress(largeBlob, (percent) => {
    progressEvents.push(percent);
  });

  console.assert(progressEvents.length > 0, 'Should have progress events');
  console.log('Exercise 2 passed!');
}

// =============================================================================
// Exercise 3: Data Conversion Utilities
// =============================================================================

/**
 * Convert between different data formats
 */
const DataConverter = {
  /**
   * Convert Blob to Base64 data URL
   */
  async blobToBase64(blob) {
    // TODO: Use FileReader to get data URL
    throw new Error('Not implemented');
  },

  /**
   * Convert Base64 data URL to Blob
   */
  base64ToBlob(dataUrl) {
    // TODO:
    // 1. Extract MIME type from data URL
    // 2. Decode base64 to binary string
    // 3. Convert to Uint8Array
    // 4. Create and return Blob
    throw new Error('Not implemented');
  },

  /**
   * Convert ArrayBuffer to hex string
   */
  bufferToHex(buffer) {
    // TODO: Convert each byte to 2-digit hex
    throw new Error('Not implemented');
  },

  /**
   * Convert hex string to ArrayBuffer
   */
  hexToBuffer(hex) {
    // TODO: Parse hex pairs to bytes
    throw new Error('Not implemented');
  },
};

// Test
async function testExercise3() {
  const testBlob = new Blob(['Hello'], { type: 'text/plain' });
  const base64 = await DataConverter.blobToBase64(testBlob);
  console.assert(base64.startsWith('data:text/plain'), 'Should be data URL');

  const restored = DataConverter.base64ToBlob(base64);
  const text = await restored.text();
  console.assert(text === 'Hello', 'Content should be restored');

  const buffer = new Uint8Array([0x48, 0x65, 0x6c]).buffer;
  const hex = DataConverter.bufferToHex(buffer);
  console.assert(hex === '48656c', 'Hex should match');

  const backToBuffer = DataConverter.hexToBuffer(hex);
  console.assert(backToBuffer.byteLength === 3, 'Buffer length should match');

  console.log('Exercise 3 passed!');
}

// =============================================================================
// Exercise 4: File Download Utilities
// =============================================================================

/**
 * File download manager
 */
class DownloadManager {
  /**
   * Download text as a file
   */
  downloadText(content, filename) {
    // TODO:
    // 1. Create a text Blob
    // 2. Create object URL
    // 3. Create and click download link
    // 4. Cleanup URL
    throw new Error('Not implemented');
  }

  /**
   * Download JSON data
   */
  downloadJSON(data, filename) {
    // TODO: Convert to JSON and download
    throw new Error('Not implemented');
  }

  /**
   * Download CSV from array of objects
   */
  downloadCSV(objects, filename) {
    // TODO:
    // 1. Extract headers from first object
    // 2. Create CSV string with headers and rows
    // 3. Handle values with commas (wrap in quotes)
    // 4. Download as CSV file
    throw new Error('Not implemented');
  }

  /**
   * Download Blob with custom filename
   */
  downloadBlob(blob, filename) {
    // TODO: Create URL, trigger download, cleanup
    throw new Error('Not implemented');
  }
}

// Test (visual verification in browser)
async function testExercise4() {
  const manager = new DownloadManager();

  // These should trigger downloads in browser
  // manager.downloadText('Hello, World!', 'hello.txt');
  // manager.downloadJSON({ test: true }, 'data.json');
  // manager.downloadCSV([{ name: 'John', age: 30 }], 'users.csv');

  console.log('Exercise 4: Run in browser to test downloads');
}

// =============================================================================
// Exercise 5: Blob Chunking and Streaming
// =============================================================================

/**
 * Read large Blob in chunks
 *
 * @param {Blob} blob - Blob to read
 * @param {number} chunkSize - Size of each chunk
 * @returns {AsyncGenerator<{index: number, data: ArrayBuffer, isLast: boolean}>}
 */
async function* readChunks(blob, chunkSize = 1024) {
  // TODO:
  // 1. Calculate number of chunks
  // 2. Use a loop to slice and yield each chunk
  // 3. Include index, data, and isLast flag

  throw new Error('Not implemented');
}

/**
 * Concatenate multiple Blobs
 *
 * @param {Blob[]} blobs - Array of Blobs
 * @param {string} type - MIME type for result
 * @returns {Blob}
 */
function concatenateBlobs(blobs, type) {
  // TODO: Create new Blob from array of Blobs
  throw new Error('Not implemented');
}

/**
 * Split Blob into equal parts
 *
 * @param {Blob} blob - Blob to split
 * @param {number} numParts - Number of parts
 * @returns {Blob[]}
 */
function splitBlob(blob, numParts) {
  // TODO: Calculate part size and slice Blob
  throw new Error('Not implemented');
}

// Test
async function testExercise5() {
  const testBlob = new Blob(['0123456789'], { type: 'text/plain' });

  // Test chunking
  const chunks = [];
  for await (const chunk of readChunks(testBlob, 3)) {
    chunks.push(chunk);
  }
  console.assert(chunks.length === 4, 'Should have 4 chunks');
  console.assert(chunks[3].isLast === true, 'Last chunk should be marked');

  // Test concatenate
  const parts = [new Blob(['Hello']), new Blob([' ']), new Blob(['World'])];
  const combined = concatenateBlobs(parts, 'text/plain');
  const text = await combined.text();
  console.assert(text === 'Hello World', 'Should concatenate correctly');

  // Test split
  const splitParts = splitBlob(testBlob, 2);
  console.assert(splitParts.length === 2, 'Should have 2 parts');

  console.log('Exercise 5 passed!');
}

// =============================================================================
// Exercise 6: Image Processing
// =============================================================================

/**
 * Image processor class
 */
class ImageProcessor {
  /**
   * Resize image while maintaining aspect ratio
   */
  async resize(imageBlob, maxWidth, maxHeight) {
    // TODO:
    // 1. Load image from Blob URL
    // 2. Calculate new dimensions maintaining aspect ratio
    // 3. Draw to canvas at new size
    // 4. Return as Blob
    throw new Error('Not implemented');
  }

  /**
   * Convert image to different format
   */
  async convert(imageBlob, targetType, quality = 0.9) {
    // TODO:
    // 1. Load image
    // 2. Draw to canvas
    // 3. Convert with canvas.toBlob(callback, targetType, quality)
    throw new Error('Not implemented');
  }

  /**
   * Create thumbnail
   */
  async createThumbnail(imageBlob, size = 100) {
    // TODO: Resize to fit within size x size square
    throw new Error('Not implemented');
  }

  /**
   * Get image dimensions
   */
  async getDimensions(imageBlob) {
    // TODO: Load image and return { width, height }
    throw new Error('Not implemented');
  }
}

// Test (requires actual image blob)
async function testExercise6() {
  console.log('Exercise 6: Requires browser with actual image file');
  console.log('Example usage:');
  console.log('  const processor = new ImageProcessor();');
  console.log(
    '  const thumbnail = await processor.createThumbnail(imageFile, 150);'
  );
}

// =============================================================================
// Exercise 7: File Type Validation
// =============================================================================

/**
 * File type validator
 */
class FileValidator {
  // Magic byte signatures
  static SIGNATURES = {
    'image/jpeg': [0xff, 0xd8, 0xff],
    'image/png': [0x89, 0x50, 0x4e, 0x47],
    'image/gif': [0x47, 0x49, 0x46, 0x38],
    'application/pdf': [0x25, 0x50, 0x44, 0x46],
  };

  /**
   * Detect file type by magic bytes
   */
  async detectType(file) {
    // TODO:
    // 1. Read first 8 bytes of file
    // 2. Compare against known signatures
    // 3. Return detected type or 'unknown'
    throw new Error('Not implemented');
  }

  /**
   * Validate file against allowed types
   */
  async validate(file, allowedTypes) {
    // TODO:
    // 1. Detect actual type
    // 2. Check if in allowed list (support wildcards like 'image/*')
    // 3. Return { valid: boolean, detectedType: string }
    throw new Error('Not implemented');
  }

  /**
   * Validate file size
   */
  validateSize(file, maxSizeBytes) {
    // TODO: Check if file.size <= maxSizeBytes
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise7() {
  const validator = new FileValidator();

  // Create test PNG-like blob (with PNG signature)
  const pngSignature = new Uint8Array([
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
  ]);
  const pngBlob = new Blob([pngSignature], { type: 'image/png' });

  const detected = await validator.detectType(pngBlob);
  console.assert(detected === 'image/png', 'Should detect PNG');

  const validation = await validator.validate(pngBlob, ['image/*']);
  console.assert(validation.valid === true, 'PNG should be valid for image/*');

  console.assert(
    validator.validateSize(pngBlob, 100),
    'Small blob should pass size check'
  );
  console.assert(
    !validator.validateSize(pngBlob, 1),
    'Should fail with 1 byte limit'
  );

  console.log('Exercise 7 passed!');
}

// =============================================================================
// Exercise 8: File Upload Handler
// =============================================================================

/**
 * Advanced file upload handler
 */
class FileUploadHandler {
  constructor(options = {}) {
    this.maxSize = options.maxSize || 10 * 1024 * 1024; // 10MB
    this.allowedTypes = options.allowedTypes || ['*/*'];
    this.chunkSize = options.chunkSize || 1024 * 1024; // 1MB chunks
  }

  /**
   * Validate file before upload
   */
  async validate(file) {
    // TODO:
    // 1. Check file size against maxSize
    // 2. Validate file type against allowedTypes
    // 3. Return { valid: boolean, errors: string[] }
    throw new Error('Not implemented');
  }

  /**
   * Upload file with progress
   */
  async upload(file, url, onProgress) {
    // TODO:
    // 1. Validate file
    // 2. Create FormData
    // 3. Use XHR with upload progress event
    // 4. Return response or throw error
    throw new Error('Not implemented');
  }

  /**
   * Upload large file in chunks
   */
  async uploadChunked(file, url, onProgress) {
    // TODO:
    // 1. Split file into chunks
    // 2. Upload each chunk with metadata (index, total, fileId)
    // 3. Report overall progress
    // 4. Return final response
    throw new Error('Not implemented');
  }

  /**
   * Upload multiple files
   */
  async uploadMultiple(files, url, onProgress) {
    // TODO:
    // 1. Validate all files
    // 2. Upload each sequentially
    // 3. Report aggregate progress
    // 4. Return array of results
    throw new Error('Not implemented');
  }
}

// Test
async function testExercise8() {
  const handler = new FileUploadHandler({
    maxSize: 5 * 1024 * 1024,
    allowedTypes: ['image/*', 'application/pdf'],
  });

  // Test validation
  const smallImage = new File(['test'], 'test.png', { type: 'image/png' });
  const validation = await handler.validate(smallImage);
  console.assert(validation.valid === true, 'Small image should be valid');

  const largeFile = new File([new ArrayBuffer(10 * 1024 * 1024)], 'large.bin');
  const largeValidation = await handler.validate(largeFile);
  console.assert(
    largeValidation.valid === false,
    'Large file should be invalid'
  );

  console.log('Exercise 8 passed!');
}

// =============================================================================
// Exercise 9: Blob URL Manager
// =============================================================================

/**
 * Manage Blob URLs to prevent memory leaks
 */
class BlobURLManager {
  constructor() {
    this.urls = new Map(); // url -> { blob, createdAt, tag }
  }

  /**
   * Create a managed Blob URL
   */
  create(blob, tag = 'default') {
    // TODO:
    // 1. Create object URL
    // 2. Store with metadata
    // 3. Return URL
    throw new Error('Not implemented');
  }

  /**
   * Revoke a specific URL
   */
  revoke(url) {
    // TODO: Revoke and remove from tracking
    throw new Error('Not implemented');
  }

  /**
   * Revoke all URLs with a specific tag
   */
  revokeByTag(tag) {
    // TODO: Find and revoke all URLs with given tag
    throw new Error('Not implemented');
  }

  /**
   * Revoke URLs older than maxAge milliseconds
   */
  revokeOld(maxAge) {
    // TODO: Find and revoke old URLs
    throw new Error('Not implemented');
  }

  /**
   * Revoke all URLs
   */
  revokeAll() {
    // TODO: Revoke and clear all
    throw new Error('Not implemented');
  }

  /**
   * Get count of active URLs
   */
  get count() {
    return this.urls.size;
  }
}

// Test
async function testExercise9() {
  const manager = new BlobURLManager();

  const blob1 = new Blob(['Test 1']);
  const blob2 = new Blob(['Test 2']);
  const blob3 = new Blob(['Test 3']);

  const url1 = manager.create(blob1, 'temp');
  const url2 = manager.create(blob2, 'temp');
  const url3 = manager.create(blob3, 'permanent');

  console.assert(manager.count === 3, 'Should have 3 URLs');

  manager.revokeByTag('temp');
  console.assert(manager.count === 1, 'Should have 1 URL after revoking temp');

  manager.revokeAll();
  console.assert(manager.count === 0, 'Should have 0 URLs after revokeAll');

  console.log('Exercise 9 passed!');
}

// =============================================================================
// Run All Tests
// =============================================================================

async function runAllTests() {
  console.log('Running Blob and File API Exercises...\n');

  try {
    await testExercise1();
    await testExercise2();
    await testExercise3();
    await testExercise4();
    await testExercise5();
    await testExercise6();
    await testExercise7();
    await testExercise8();
    await testExercise9();

    console.log('\nāœ… All exercises completed!');
  } catch (error) {
    console.error('\nāŒ Test failed:', error.message);
  }
}

// Export
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    createAndReadBlob,
    createJsonBlob,
    readWithProgress,
    DataConverter,
    DownloadManager,
    readChunks,
    concatenateBlobs,
    splitBlob,
    ImageProcessor,
    FileValidator,
    FileUploadHandler,
    BlobURLManager,
    runAllTests,
  };
}

// Uncomment to run
// runAllTests();
Exercises - JavaScript Tutorial | DeepML