Web API Polyfills and Implementations

1. Fetch API and AbortController Polyfills

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 Polyfills

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 Polyfills

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 Implementations

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 API

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 Polyfills

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