Mastering JavaScript Polyfills

Introduction
Polyfills are crucial pieces of code that allow developers to use modern JavaScript features in environments that don't natively support them. In this comprehensive tutorial, we'll explore how to create polyfills for commonly used Array and Promise methods, understanding their internal workings along the way.
By the end of this guide, you'll have a deep understanding of how these built-in methods work under the hood and how to implement them from scratch.
Part 1: Array Methods Polyfills
1. Array.prototype.forEach()
The forEach() method executes a provided function once for each array element. Unlike map(), it doesn't return a new array.
How it Works
Iterates through each element in the array
Executes the callback function for each element
Handles sparse arrays (skips missing indices)
Returns
undefined
Implementation
Array.prototype.myForEach = function (callback) {
if (this == null) {
throw new TypeError(
"Array.prototype.myForEach called on null or undefined"
);
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
callback(obj[i], i, obj);
}
}
};
Example Usage
const arr = [1, 2, 3, 4, 5];
arr.myForEach((element, index, self) => {
console.log(`Index: ${index}, Element: ${element}`);
});
Key Concepts
this == null: Checks for bothnullandundefinedobj.length >>> 0: The zero-fill right shift ensures the length is a valid unsigned 32-bit integeri in obj: Checks if the property exists, handling sparse arrays correctly
2. Array.prototype.map()
The map() method creates a new array with the results of calling a provided function on every element.
How it Works
Creates a new array of the same length
Transforms each element using the callback function
Preserves sparse array structure
Returns the new array
Implementation
Array.prototype.myMap = function (callback) {
if (this == null) {
throw new TypeError("Array.prototype.myMap called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
const newArray = new Array(len);
for (let i = 0; i < len; i++) {
if (i in obj) {
newArray[i] = callback(obj[i], i, obj);
}
}
return newArray;
};
Example Usage
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.myMap((item, idx, self) => {
return item * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
const squared = numbers.myMap(item => item ** 2);
console.log(squared); // [1, 4, 9, 16, 25]
Real-World Example
const users = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Charlie", age: 35 }
];
const userNames = users.myMap(user => user.name);
console.log(userNames); // ["Alice", "Bob", "Charlie"]
3. Array.prototype.filter()
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
How it Works
Tests each element with the callback function
Includes elements that return
trueCreates a new array (original array unchanged)
Returns empty array if no elements pass
Implementation
Array.prototype.myFilter = function (callback) {
if (this == null) {
throw new TypeError("Array.prototype.myFilter called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
const newArray = [];
for (let i = 0; i < len; i++) {
if (i in obj) {
if (callback(obj[i], i, obj)) {
newArray.push(obj[i]);
}
}
}
return newArray;
};
Example Usage
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.myFilter((item, idx, self) => {
return item % 2 === 0;
});
console.log(evenNumbers); // [2, 4, 6]
const greaterThanThree = numbers.myFilter(item => item > 3);
console.log(greaterThanThree); // [4, 5, 6]
Real-World Example
const products = [
{ name: "Laptop", price: 1000, inStock: true },
{ name: "Phone", price: 500, inStock: false },
{ name: "Tablet", price: 300, inStock: true },
{ name: "Monitor", price: 200, inStock: true }
];
const availableProducts = products.myFilter(product => product.inStock);
console.log(availableProducts);
// [{ name: "Laptop", ... }, { name: "Tablet", ... }, { name: "Monitor", ... }]
const affordableProducts = products.myFilter(product => product.price <= 500);
console.log(affordableProducts);
4. Array.prototype.find()
The find() method returns the first element in the array that satisfies the provided testing function.
How it Works
Iterates through the array
Returns the first element where callback returns
trueReturns
undefinedif no element is foundStops iteration once element is found
Implementation
Array.prototype.myFind = function (callback) {
if (this == null) {
throw new TypeError("Array.prototype.myFind called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
if (callback(obj[i], i, obj)) {
return obj[i];
}
}
}
return undefined;
};
Example Usage
const employees = [
{ empId: 1, name: "Alice", department: "Engineering" },
{ empId: 2, name: "Bob", department: "Sales" },
{ empId: 3, name: "Charlie", department: "Engineering" }
];
const employee = employees.myFind((item, idx, self) => {
return item.empId === 2;
});
console.log(employee); // { empId: 2, name: "Bob", department: "Sales" }
const engineer = employees.myFind(emp => emp.department === "Engineering");
console.log(engineer); // { empId: 1, name: "Alice", department: "Engineering" }
5. Array.prototype.some()
The some() method tests whether at least one element in the array passes the test implemented by the provided function.
How it Works
Returns
trueif at least one element passes the testReturns
falseif all elements fail the testShort-circuits (stops) when first
trueis foundReturns
falsefor empty arrays
Implementation
Array.prototype.mySome = function (callback) {
if (this == null) {
throw new TypeError("Array.prototype.mySome called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
if (callback(obj[i], i, obj)) {
return true;
}
}
}
return false;
};
Example Usage
const numbers = [1, 2, 3, 4, 5, 10];
const hasLargeNumber = numbers.mySome((item, idx, self) => {
return item > 8;
});
console.log(hasLargeNumber); // true
const hasNegative = numbers.mySome(item => item < 0);
console.log(hasNegative); // false
Real-World Example
const orders = [
{ id: 1, status: "pending", total: 100 },
{ id: 2, status: "shipped", total: 200 },
{ id: 3, status: "delivered", total: 150 }
];
const hasPendingOrders = orders.mySome(order => order.status === "pending");
console.log(hasPendingOrders); // true
const hasExpensiveOrder = orders.mySome(order => order.total > 500);
console.log(hasExpensiveOrder); // false
6. Array.prototype.every()
The every() method tests whether all elements in the array pass the test implemented by the provided function.
How it Works
Returns
trueonly if all elements pass the testReturns
falseif any element failsShort-circuits when first
falseis foundReturns
truefor empty arrays
Implementation
Array.prototype.myEvery = function (callback) {
if (this == null) {
throw new TypeError("Array.prototype.myEvery called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
if (!callback(obj[i], i, obj)) {
return false;
}
}
}
return true;
};
Example Usage
const numbers = [1, 2, 3, 4, 5];
const allPositive = numbers.myEvery((item, idx, self) => {
return item > 0;
});
console.log(allPositive); // true
const allGreaterThanOne = numbers.myEvery(item => item > 1);
console.log(allGreaterThanOne); // false
Real-World Example
const users = [
{ name: "Alice", age: 25, verified: true },
{ name: "Bob", age: 30, verified: true },
{ name: "Charlie", age: 35, verified: true }
];
const allVerified = users.myEvery(user => user.verified);
console.log(allVerified); // true
const allAdults = users.myEvery(user => user.age >= 18);
console.log(allAdults); // true
7. Array.prototype.reduce()
The reduce() method executes a reducer function on each element of the array, resulting in a single output value.
How it Works
Accumulates values into a single result
Takes an optional initial value
If no initial value, uses first array element
Processes array left to right
Implementation
Array.prototype.myReduce = function (callback, initialValue) {
if (this == null) {
throw new TypeError("Array.prototype.myReduce called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
let accumulator;
let startIdx = 0;
if (arguments.length > 1) {
accumulator = initialValue;
} else {
// Find first existing element
while (startIdx < len && !(startIdx in obj)) {
startIdx++;
}
if (startIdx >= len) {
throw new TypeError("Reduce of empty array with no initial value");
}
accumulator = obj[startIdx++];
}
for (let i = startIdx; i < len; i++) {
if (i in obj) {
accumulator = callback(accumulator, obj[i], i, obj);
}
}
return accumulator;
};
Example Usage
const numbers = [1, 2, 3, 4, 5];
// Sum all numbers
const sum = numbers.myReduce((acc, curr, idx, self) => {
return acc + curr;
}, 0);
console.log(sum); // 15
// Product of all numbers
const product = numbers.myReduce((acc, curr) => acc * curr, 1);
console.log(product); // 120
Real-World Examples
// Counting occurrences
const fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
const fruitCount = fruits.myReduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(fruitCount);
// { apple: 3, banana: 2, orange: 1 }
// Flattening nested arrays
const nested = [[1, 2], [3, 4], [5, 6]];
const flattened = nested.myReduce((acc, curr) => {
return acc.concat(curr);
}, []);
console.log(flattened); // [1, 2, 3, 4, 5, 6]
// Calculating total cart value
const cart = [
{ item: "Laptop", price: 1000, quantity: 1 },
{ item: "Mouse", price: 25, quantity: 2 },
{ item: "Keyboard", price: 75, quantity: 1 }
];
const totalCost = cart.myReduce((total, product) => {
return total + (product.price * product.quantity);
}, 0);
console.log(totalCost); // 1125
8. Array.prototype.flat()
The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.
How it Works
Recursively flattens nested arrays
Takes a depth parameter (default: 1)
Can use
Infinityto flatten all levelsHandles sparse arrays
Implementation
Array.prototype.myFlat = function (depth) {
if (this == null) {
throw new TypeError("Array.prototype.myFlat called on null or undefined");
}
let maxDepth;
if (depth === undefined) {
maxDepth = 1;
} else if (depth === Infinity) {
maxDepth = Infinity;
} else {
maxDepth = Math.floor(Number(depth));
}
const newArray = [];
const sourceArray = Object(this);
const len = sourceArray.length >>> 0;
function flatHelper(arr, currDepth) {
for (let i = 0; i < arr.length; i++) {
if (i in arr) {
if (Array.isArray(arr[i]) && currDepth < maxDepth) {
flatHelper(arr[i], currDepth + 1);
} else {
newArray.push(arr[i]);
}
}
}
}
flatHelper(sourceArray, 0);
return newArray;
};
Example Usage
const arr = [1, 2, [3, 4, [5, 6, 7]], 8];
// Flatten one level
const flat1 = arr.myFlat(1);
console.log(flat1); // [1, 2, 3, 4, [5, 6, 7], 8]
// Flatten two levels
const flat2 = arr.myFlat(2);
console.log(flat2); // [1, 2, 3, 4, 5, 6, 7, 8]
// Flatten all levels
const flatAll = arr.myFlat(Infinity);
console.log(flatAll); // [1, 2, 3, 4, 5, 6, 7, 8]
Real-World Example
const nestedCategories = [
"Electronics",
["Computers", ["Laptops", "Desktops"]],
["Phones", ["iPhone", "Android"]],
"Accessories"
];
const allCategories = nestedCategories.myFlat(Infinity);
console.log(allCategories);
// ["Electronics", "Computers", "Laptops", "Desktops", "Phones", "iPhone", "Android", "Accessories"]
Part 2: Promise Methods Polyfills
1. Promise.all()
Promise.all() takes an iterable of promises and returns a single Promise that resolves when all promises resolve, or rejects when any promise rejects.
How it Works
Waits for all promises to resolve
Rejects immediately if any promise rejects
Resolves with an array of results in order
Returns empty array for empty input
Implementation
Promise.myAll = function (iterable) {
return new Promise((resolve, reject) => {
const promises = Array.from(iterable);
const len = promises.length;
if (len === 0) {
resolve([]);
return;
}
const results = new Array(len);
let resolvedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = value;
resolvedCount++;
if (resolvedCount === len) {
resolve(results);
}
})
.catch((error) => {
reject(error);
});
});
});
};
Example Usage
const promise1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise 1 resolved");
}, 1000);
});
};
const promise2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise 2 resolved");
}, 2000);
});
};
const promise3 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise 3 resolved");
}, 1500);
});
};
Promise.myAll([promise1(), promise2(), promise3()])
.then((results) => {
console.log("All resolved:", results);
// All resolved: ["Promise 1 resolved", "Promise 2 resolved", "Promise 3 resolved"]
})
.catch((error) => {
console.error("One rejected:", error);
});
Real-World Example
// Fetching multiple API endpoints
const fetchUser = fetch('/api/user/1').then(res => res.json());
const fetchPosts = fetch('/api/posts').then(res => res.json());
const fetchComments = fetch('/api/comments').then(res => res.json());
Promise.myAll([fetchUser, fetchPosts, fetchComments])
.then(([user, posts, comments]) => {
console.log('User:', user);
console.log('Posts:', posts);
console.log('Comments:', comments);
})
.catch(error => {
console.error('Failed to fetch data:', error);
});
2. Promise.allSettled()
Promise.allSettled() waits for all promises to settle (either resolve or reject) and returns their results.
How it Works
Waits for all promises to settle
Never rejects (always resolves)
Returns array of objects with status and value/reason
Useful when you need all results regardless of success
Implementation
Promise.myAllSettled = function (iterable) {
return new Promise((resolve, reject) => {
const promises = Array.from(iterable);
const len = promises.length;
if (len === 0) {
resolve([]);
return;
}
const results = new Array(len);
let settledCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = { status: "fulfilled", value: value };
})
.catch((reason) => {
results[index] = { status: "rejected", reason: reason };
})
.finally(() => {
settledCount++;
if (settledCount === len) {
resolve(results);
}
});
});
});
};
Example Usage
const promise1 = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("Success 1"), 1000);
});
};
const promise2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Error 2"), 1000);
});
};
const promise3 = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("Success 3"), 1000);
});
};
Promise.myAllSettled([promise1(), promise2(), promise3()])
.then((results) => {
console.log("All settled:", results);
/* Output:
[
{ status: "fulfilled", value: "Success 1" },
{ status: "rejected", reason: "Error 2" },
{ status: "fulfilled", value: "Success 3" }
]
*/
});
Real-World Example
// Processing multiple independent operations
const operations = [
saveUserProfile(userData),
uploadProfilePicture(imageFile),
sendWelcomeEmail(email),
createUserDirectory(userId)
];
Promise.myAllSettled(operations)
.then((results) => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Operation ${index + 1} succeeded:`, result.value);
} else {
console.error(`Operation ${index + 1} failed:`, result.reason);
}
});
});
3. Promise.race()
Promise.race() returns a promise that resolves or rejects as soon as one of the promises resolves or rejects.
How it Works
Returns result of first settled promise
Can resolve or reject based on first result
Other promises continue but results are ignored
Useful for timeouts and quick responses
Implementation
Promise.myRace = function (iterable) {
return new Promise((resolve, reject) => {
const promises = Array.from(iterable);
const len = promises.length;
if (len === 0) {
return;
}
promises.forEach((promise) => {
Promise.resolve(promise)
.then((value) => {
resolve(value);
})
.catch((reason) => {
reject(reason);
});
});
});
};
Example Usage
const fast = new Promise((resolve) => {
setTimeout(() => resolve("Fast promise won"), 100);
});
const slow = new Promise((resolve) => {
setTimeout(() => resolve("Slow promise"), 1000);
});
const failure = new Promise((resolve, reject) => {
setTimeout(() => reject("Failed promise"), 500);
});
Promise.myRace([fast, slow, failure])
.then((result) => {
console.log("Winner:", result); // "Fast promise won"
})
.catch((error) => {
console.error("First to fail:", error);
});
Real-World Example
// Implementing request timeout
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Request timeout")), timeout);
});
return Promise.myRace([fetchPromise, timeoutPromise]);
}
fetchWithTimeout('/api/data', 3000)
.then(response => response.json())
.then(data => console.log('Data:', data))
.catch(error => console.error('Error:', error.message));
4. Promise.any()
Promise.any() takes an iterable of promises and returns a promise that resolves when any of the promises resolves, or rejects if all promises reject.
How it Works
Resolves with first successful promise
Ignores rejected promises
Rejects only if all promises reject
Returns AggregateError with all rejection reasons
Implementation
Promise.myAny = function (iterable) {
return new Promise((resolve, reject) => {
const promises = Array.from(iterable);
const len = promises.length;
if (len === 0) {
reject(new AggregateError([], "All promises were rejected"));
return;
}
const errors = new Array(len);
let rejectCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
resolve(value);
})
.catch((reason) => {
rejectCount++;
errors[index] = reason;
if (rejectCount === len) {
reject(new AggregateError(errors, "All promises were rejected"));
}
});
});
});
};
Example Usage
const promise1 = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("First success"), 2000);
});
};
const promise2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Second failed"), 1000);
});
};
const promise3 = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("Third success"), 1000);
});
};
Promise.myAny([promise1(), promise2(), promise3()])
.then((result) => {
console.log("First success:", result); // "Third success"
})
.catch((error) => {
console.error("All failed:", error);
});
Real-World Example
// Trying multiple API endpoints
function fetchFromMultipleSources(endpoints) {
const promises = endpoints.map(endpoint =>
fetch(endpoint).then(res => res.json())
);
return Promise.myAny(promises);
}
const apiEndpoints = [
'https://api1.example.com/data',
'https://api2.example.com/data',
'https://api3.example.com/data'
];
fetchFromMultipleSources(apiEndpoints)
.then(data => {
console.log('Successfully fetched data:', data);
})
.catch(error => {
console.error('All endpoints failed:', error);
});
Key Differences Between Promise Methods
| Method | Resolves When | Rejects When | Use Case |
| Promise.all() | All promises resolve | Any promise rejects | All operations must succeed |
| Promise.allSettled() | All promises settle | Never rejects | Need all results regardless |
| Promise.race() | First promise settles | First promise rejects | Need fastest response |
| Promise.any() | First promise resolves | All promises reject | Need any successful result |
Conclusion
Understanding these polyfills gives you deeper insight into how JavaScript works internally. These implementations follow the ECMAScript specifications and handle edge cases properly.
Key takeaways:
Array methods transform data without mutating originals
Promise methods coordinate asynchronous operations
Proper error handling is crucial
Understanding these implementations makes you a better JavaScript developer
Practice implementing these polyfills yourself and experiment with different use cases to solidify your understanding!




