Modern Web APIs (Fetch, IntersectionObserver, URL)
1. Fetch API and AbortController Implementation
| Method/Feature | Syntax | Returns | Use Case |
|---|---|---|---|
| fetch() | fetch(url, options) |
Promise<Response> | Modern HTTP requests |
| Response.json() | response.json() |
Promise<any> | Parse JSON response body |
| Response.text() | response.text() |
Promise<string> | Get response as text |
| AbortController NEW | new AbortController() |
Controller with signal | Cancel fetch requests |
| AbortSignal | controller.signal |
Signal object | Pass to fetch for cancellation |
| Legacy: XMLHttpRequest | new XMLHttpRequest() |
XHR object | Fallback for fetch polyfill |
Example: Simplified fetch polyfill using XMLHttpRequest
// Simplified fetch polyfill (basic implementation)
(function() {
if ('fetch' in window) return;
window.fetch = function(url, options) {
options = options || {};
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
var method = options.method || 'GET';
xhr.open(method, url, true);
// Set headers
if (options.headers) {
Object.keys(options.headers).forEach(function(key) {
xhr.setRequestHeader(key, options.headers[key]);
});
}
// Handle response
xhr.onload = function() {
var response = {
ok: xhr.status >= 200 && xhr.status < 300,
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders()),
url: xhr.responseURL,
text: function() {
return Promise.resolve(xhr.responseText);
},
json: function() {
return Promise.resolve(JSON.parse(xhr.responseText));
},
blob: function() {
return Promise.resolve(new Blob([xhr.response]));
},
arrayBuffer: function() {
return Promise.resolve(xhr.response);
}
};
resolve(response);
};
xhr.onerror = function() {
reject(new TypeError('Network request failed'));
};
xhr.ontimeout = function() {
reject(new TypeError('Network request timeout'));
};
// Send request
xhr.send(options.body || null);
});
};
function parseHeaders(rawHeaders) {
var headers = {};
var headerLines = rawHeaders.split('\r\n');
headerLines.forEach(function(line) {
var parts = line.split(': ');
if (parts.length === 2) {
headers[parts[0]] = parts[1];
}
});
return headers;
}
})();
// Usage
fetch('/api/data')
.then(function(response) {
if (!response.ok) throw new Error('HTTP error');
return response.json();
})
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.error('Fetch error:', error);
});
Example: AbortController polyfill (simplified)
// AbortController polyfill (basic implementation)
(function() {
if ('AbortController' in window) return;
function AbortSignal() {
this.aborted = false;
this.onabort = null;
this._listeners = [];
}
AbortSignal.prototype.addEventListener = function(type, listener) {
if (type === 'abort') {
this._listeners.push(listener);
}
};
AbortSignal.prototype.removeEventListener = function(type, listener) {
if (type === 'abort') {
var index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
}
};
AbortSignal.prototype.dispatchEvent = function() {
if (this.onabort) this.onabort();
this._listeners.forEach(function(listener) {
listener();
});
};
function AbortController() {
this.signal = new AbortSignal();
}
AbortController.prototype.abort = function() {
if (this.signal.aborted) return;
this.signal.aborted = true;
this.signal.dispatchEvent();
};
window.AbortController = AbortController;
window.AbortSignal = AbortSignal;
})();
// Usage with fetch
var controller = new AbortController();
var signal = controller.signal;
fetch('/api/data', { signal: signal })
.then(function(response) {
return response.json();
})
.catch(function(error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
}
});
// Cancel request after 5 seconds
setTimeout(function() {
controller.abort();
}, 5000);
Note: For production use, install
whatwg-fetch and
abortcontroller-polyfill packages for full spec compliance. The examples above are simplified for
understanding.
2. URL and URLSearchParams Constructor
| API | Method/Property | Description | Example |
|---|---|---|---|
| URL Constructor | new URL(url, base) |
Parse and manipulate URLs | new URL('/path', 'https://example.com') |
| URL.href | url.href |
Full URL string | 'https://example.com/path' |
| URL.searchParams | url.searchParams |
URLSearchParams object | Access query parameters |
| URLSearchParams | new URLSearchParams(query) |
Parse/build query strings | new URLSearchParams('?a=1&b=2') |
| get/set/has/delete | params.get('key') |
Manipulate query parameters | Get, set, check, remove params |
| toString() | params.toString() |
Serialize to query string | 'a=1&b=2' |
Example: URLSearchParams polyfill
// URLSearchParams polyfill
(function() {
if ('URLSearchParams' in window) return;
function URLSearchParams(init) {
this._entries = {};
if (typeof init === 'string') {
if (init.charAt(0) === '?') {
init = init.slice(1);
}
var pairs = init.split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
if (pair.length === 2) {
this.append(
decodeURIComponent(pair[0]),
decodeURIComponent(pair[1])
);
}
}
} else if (init) {
Object.keys(init).forEach(function(key) {
this.append(key, init[key]);
}, this);
}
}
URLSearchParams.prototype.append = function(name, value) {
if (!this._entries[name]) {
this._entries[name] = [];
}
this._entries[name].push(String(value));
};
URLSearchParams.prototype.delete = function(name) {
delete this._entries[name];
};
URLSearchParams.prototype.get = function(name) {
return this._entries[name] ? this._entries[name][0] : null;
};
URLSearchParams.prototype.getAll = function(name) {
return this._entries[name] || [];
};
URLSearchParams.prototype.has = function(name) {
return name in this._entries;
};
URLSearchParams.prototype.set = function(name, value) {
this._entries[name] = [String(value)];
};
URLSearchParams.prototype.forEach = function(callback, thisArg) {
Object.keys(this._entries).forEach(function(name) {
this._entries[name].forEach(function(value) {
callback.call(thisArg, value, name, this);
}, this);
}, this);
};
URLSearchParams.prototype.toString = function() {
var pairs = [];
this.forEach(function(value, name) {
pairs.push(
encodeURIComponent(name) + '=' + encodeURIComponent(value)
);
});
return pairs.join('&');
};
window.URLSearchParams = URLSearchParams;
})();
// Usage
var params = new URLSearchParams('?search=test&page=2');
console.log(params.get('search')); // 'test'
params.set('page', '3');
params.append('filter', 'active');
console.log(params.toString()); // 'search=test&page=3&filter=active'
Example: Basic URL polyfill
// Basic URL polyfill (simplified)
(function() {
if ('URL' in window && 'searchParams' in URL.prototype) return;
function URL(url, base) {
var doc = document.implementation.createHTMLDocument('');
if (base) {
var baseElement = doc.createElement('base');
baseElement.href = base;
doc.head.appendChild(baseElement);
}
var anchorElement = doc.createElement('a');
anchorElement.href = url;
doc.body.appendChild(anchorElement);
this.href = anchorElement.href;
this.protocol = anchorElement.protocol;
this.host = anchorElement.host;
this.hostname = anchorElement.hostname;
this.port = anchorElement.port;
this.pathname = anchorElement.pathname;
this.search = anchorElement.search;
this.hash = anchorElement.hash;
this.origin = this.protocol + '//' + this.host;
// Add searchParams
var params = new URLSearchParams(this.search);
Object.defineProperty(this, 'searchParams', {
get: function() {
return params;
}
});
}
URL.prototype.toString = function() {
return this.href;
};
window.URL = URL;
})();
3. FormData and File API for Form Handling
| API | Method | Description | Browser Support |
|---|---|---|---|
| FormData | new FormData(form) |
Construct form data for submission | IE10+ |
| formData.append | formData.append(key, value, filename) |
Add field to form data | Most common method |
| formData.get | formData.get(key) |
Get first value for key | Modern browsers |
| formData.entries | formData.entries() |
Iterator of all entries | Modern browsers |
| File API | new File(bits, name, options) |
Create file objects | IE10+ (limited) |
| FileList | input.files |
Array-like list of files | From file inputs |
Example: FormData polyfill extensions
// FormData polyfill for missing methods (IE11)
(function() {
if ('FormData' in window) {
var originalFormData = window.FormData;
// Add get method if missing
if (!FormData.prototype.get) {
FormData.prototype.get = function(name) {
var entries = this.getAll(name);
return entries.length ? entries[0] : null;
};
}
// Add getAll method if missing
if (!FormData.prototype.getAll) {
FormData.prototype.getAll = function(name) {
// Fallback: cannot iterate in IE, return empty array
return [];
};
}
// Add has method if missing
if (!FormData.prototype.has) {
FormData.prototype.has = function(name) {
return this.get(name) !== null;
};
}
// Add set method if missing
if (!FormData.prototype.set) {
FormData.prototype.set = function(name, value, filename) {
this.delete(name);
this.append(name, value, filename);
};
}
// Add delete method if missing
if (!FormData.prototype.delete) {
FormData.prototype.delete = function(name) {
// Cannot be implemented in IE
};
}
// Add entries iterator if missing
if (!FormData.prototype.entries) {
FormData.prototype.entries = function() {
throw new Error('FormData.entries() not supported in this browser');
};
}
}
})();
// Usage
var formData = new FormData();
formData.append('username', 'john_doe');
formData.append('email', 'john@example.com');
formData.append('avatar', fileInput.files[0]);
// Send via fetch
fetch('/api/profile', {
method: 'POST',
body: formData
});
Example: File constructor polyfill
// File constructor polyfill
(function() {
try {
new File([], '');
return; // Native support
} catch(e) {}
// Polyfill File constructor
window.File = function(bits, name, options) {
options = options || {};
var blob = new Blob(bits, options);
blob.name = name;
blob.lastModified = options.lastModified || Date.now();
blob.lastModifiedDate = new Date(blob.lastModified);
// Make it look like a File
Object.defineProperty(blob, 'constructor', {
value: File
});
return blob;
};
File.prototype = Object.create(Blob.prototype);
File.prototype.constructor = File;
})();
// Usage
var file = new File(['Hello, World!'], 'hello.txt', {
type: 'text/plain',
lastModified: Date.now()
});
console.log(file.name); // 'hello.txt'
console.log(file.size); // 13
Warning: FormData iteration methods (
entries(), keys(),
values()) cannot be fully polyfilled in IE11. Use form-data polyfill
package for complete support.
4. Blob and FileReader API for Binary Data
| API | Method/Property | Description | Returns |
|---|---|---|---|
| Blob Constructor | new Blob(parts, options) |
Create binary large object | Blob instance |
| blob.slice | blob.slice(start, end, type) |
Extract portion of blob | New Blob |
| blob.text() | blob.text() |
Read blob as text | Promise<string> |
| blob.arrayBuffer() | blob.arrayBuffer() |
Read blob as ArrayBuffer | Promise<ArrayBuffer> |
| FileReader | new FileReader() |
Read file/blob contents | FileReader instance |
| readAsText | reader.readAsText(blob) |
Read as text string | Event-based result |
| readAsDataURL | reader.readAsDataURL(blob) |
Read as base64 data URL | Event-based result |
| readAsArrayBuffer | reader.readAsArrayBuffer(blob) |
Read as binary data | Event-based result |
Example: Blob.prototype methods polyfill
// Blob.prototype.text polyfill
if (!Blob.prototype.text) {
Blob.prototype.text = function() {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() {
resolve(reader.result);
};
reader.onerror = function() {
reject(reader.error);
};
reader.readAsText(this);
}.bind(this));
};
}
// Blob.prototype.arrayBuffer polyfill
if (!Blob.prototype.arrayBuffer) {
Blob.prototype.arrayBuffer = function() {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() {
resolve(reader.result);
};
reader.onerror = function() {
reject(reader.error);
};
reader.readAsArrayBuffer(this);
}.bind(this));
};
}
// Usage
var blob = new Blob(['Hello, World!'], { type: 'text/plain' });
blob.text().then(function(text) {
console.log(text); // 'Hello, World!'
});
blob.arrayBuffer().then(function(buffer) {
console.log(new Uint8Array(buffer));
});
Example: FileReader wrapper with Promise
// Promise-based FileReader wrapper
function readFileAsText(file) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function(e) {
resolve(e.target.result);
};
reader.onerror = function() {
reject(new Error('Failed to read file'));
};
reader.readAsText(file);
});
}
function readFileAsDataURL(file) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function(e) {
resolve(e.target.result);
};
reader.onerror = function() {
reject(new Error('Failed to read file'));
};
reader.readAsDataURL(file);
});
}
// Usage
var fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', function(e) {
var file = e.target.files[0];
readFileAsText(file).then(function(text) {
console.log('File contents:', text);
});
readFileAsDataURL(file).then(function(dataURL) {
var img = document.createElement('img');
img.src = dataURL;
document.body.appendChild(img);
});
});
5. requestAnimationFrame and Performance.now()
| API | Method | Purpose | Fallback |
|---|---|---|---|
| requestAnimationFrame | requestAnimationFrame(callback) |
Schedule animation callback | setTimeout with 16ms (~60fps) |
| cancelAnimationFrame | cancelAnimationFrame(id) |
Cancel scheduled animation | clearTimeout |
| performance.now() | performance.now() |
High-resolution timestamp | Date.now() or new Date().getTime() |
| performance.mark() | performance.mark(name) |
Create performance mark | Manual timestamp tracking |
| performance.measure() | performance.measure(name, start, end) |
Measure between marks | Timestamp subtraction |
Example: requestAnimationFrame polyfill with vendor prefixes
// requestAnimationFrame polyfill
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz', 'ms', 'o'];
// Check vendor prefixes
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
window[vendors[x] + 'CancelRequestAnimationFrame'];
}
// Fallback to setTimeout
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback) {
var currTime = Date.now();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
})();
// Usage
function animate(timestamp) {
// Animation logic here
element.style.left = (timestamp / 10) + 'px';
// Continue animation
requestAnimationFrame(animate);
}
// Start animation
requestAnimationFrame(animate);
Example: performance.now() polyfill
// performance.now polyfill
(function() {
if ('performance' in window === false) {
window.performance = {};
}
Date.now = (Date.now || function() {
return new Date().getTime();
});
if ('now' in window.performance === false) {
var nowOffset = Date.now();
if (performance.timing && performance.timing.navigationStart) {
nowOffset = performance.timing.navigationStart;
}
window.performance.now = function now() {
return Date.now() - nowOffset;
};
}
})();
// Usage
var start = performance.now();
// Some operation
for (var i = 0; i < 1000000; i++) {
// Work...
}
var end = performance.now();
console.log('Operation took:', (end - start), 'milliseconds');
6. IntersectionObserver and ResizeObserver APIs
| Observer | Purpose | Use Case | Polyfill Complexity |
|---|---|---|---|
| IntersectionObserver | Detect element visibility | Lazy loading, infinite scroll, analytics | Complex - use polyfill library |
| ResizeObserver | Detect element size changes | Responsive components, layout calculations | Complex - use polyfill library |
| MutationObserver | Watch DOM changes | React to DOM modifications | IE11+ native |
Example: IntersectionObserver basic polyfill concept
// Simplified IntersectionObserver polyfill (conceptual)
// For production, use: https://github.com/w3c/IntersectionObserver/tree/main/polyfill
(function() {
if ('IntersectionObserver' in window) return;
function IntersectionObserver(callback, options) {
this.callback = callback;
this.options = options || {};
this.root = this.options.root || null;
this.rootMargin = this.options.rootMargin || '0px';
this.thresholds = this.options.threshold || [0];
this.elements = [];
var self = this;
this._checkIntersections = function() {
var entries = [];
self.elements.forEach(function(element) {
var rect = element.getBoundingClientRect();
var rootBounds = self.root ?
self.root.getBoundingClientRect() :
{ top: 0, left: 0, bottom: window.innerHeight, right: window.innerWidth };
var isIntersecting = !(
rect.bottom < rootBounds.top ||
rect.top > rootBounds.bottom ||
rect.right < rootBounds.left ||
rect.left > rootBounds.right
);
entries.push({
target: element,
isIntersecting: isIntersecting,
intersectionRatio: isIntersecting ? 1 : 0,
boundingClientRect: rect,
rootBounds: rootBounds,
time: Date.now()
});
});
if (entries.length) {
self.callback(entries, self);
}
};
// Setup polling (not performant - use real polyfill)
setInterval(this._checkIntersections, 100);
}
IntersectionObserver.prototype.observe = function(element) {
if (this.elements.indexOf(element) === -1) {
this.elements.push(element);
}
};
IntersectionObserver.prototype.unobserve = function(element) {
var index = this.elements.indexOf(element);
if (index !== -1) {
this.elements.splice(index, 1);
}
};
IntersectionObserver.prototype.disconnect = function() {
this.elements = [];
};
window.IntersectionObserver = IntersectionObserver;
})();
// Usage
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
console.log('Element is visible:', entry.target);
// Lazy load image
if (entry.target.dataset.src) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
}
});
}, { threshold: 0.1 });
// Observe elements
document.querySelectorAll('img[data-src]').forEach(function(img) {
observer.observe(img);
});
Example: ResizeObserver polyfill concept
// Simplified ResizeObserver polyfill (conceptual)
// For production, use: https://github.com/que-etc/resize-observer-polyfill
(function() {
if ('ResizeObserver' in window) return;
function ResizeObserver(callback) {
this.callback = callback;
this.elements = new Map();
var self = this;
this._checkSizes = function() {
var entries = [];
self.elements.forEach(function(lastSize, element) {
var rect = element.getBoundingClientRect();
var currentSize = {
width: rect.width,
height: rect.height
};
if (lastSize.width !== currentSize.width ||
lastSize.height !== currentSize.height) {
entries.push({
target: element,
contentRect: rect
});
self.elements.set(element, currentSize);
}
});
if (entries.length) {
self.callback(entries, self);
}
};
// Poll for size changes (not performant - use real polyfill)
this.interval = setInterval(this._checkSizes, 100);
}
ResizeObserver.prototype.observe = function(element) {
var rect = element.getBoundingClientRect();
this.elements.set(element, {
width: rect.width,
height: rect.height
});
};
ResizeObserver.prototype.unobserve = function(element) {
this.elements.delete(element);
};
ResizeObserver.prototype.disconnect = function() {
clearInterval(this.interval);
this.elements.clear();
};
window.ResizeObserver = ResizeObserver;
})();
// Usage
var resizeObserver = new ResizeObserver(function(entries) {
entries.forEach(function(entry) {
console.log('Element resized:', entry.target);
console.log('New size:', entry.contentRect.width, entry.contentRect.height);
});
});
resizeObserver.observe(document.querySelector('.responsive-element'));
Warning: The IntersectionObserver and ResizeObserver polyfills shown are simplified for demonstration. They use polling which is not performant. For
production, use official polyfills:
intersection-observer and resize-observer-polyfill
npm packages.
Key Takeaways - Web APIs
- Fetch API: Use whatwg-fetch for full spec compliance with XHR fallback
- URLSearchParams: Polyfillable with manual query string parsing
- FormData: IE10+ support, iteration methods need polyfills
- Blob/FileReader: Convert callback-based APIs to Promises
- requestAnimationFrame: Fallback to setTimeout with 16ms delay
- Observers: Use official polyfills - custom implementations are complex