javascript
exercises
exercises.jsā”javascript
/**
* 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();