Topic 010: Polyfill in JS
Creating a polyfill for Promise.all() involves writing a function that behaves like the native Promise.all() method, which takes an iterable of promises (or values) and returns a single promise that resolves when all the input promises have resolved, or rejects if any of the input promises reject. Here's how you can implement such a polyfill:
if (!Promise.all) {
Promise.all = function (iterable) {
// Return a new promise
return new Promise(function (resolve, reject) {
// Check if the input is an iterable
if (!iterable || typeof iterable[Symbol.iterator] !== "function") {
return reject(new TypeError("Argument is not iterable"));
}
var results = [];
var completed = 0;
var total = 0;
var hasCalledReject = false;
// Helper function to process each promise
function processPromise(index, value) {
Promise.resolve(value)
.then(function (result) {
results[index] = result;
completed += 1;
// If all promises are resolved, resolve the returned promise
if (completed === total) {
resolve(results);
}
})
.catch(function (error) {
if (!hasCalledReject) {
hasCalledReject = true;
reject(error);
}
});
}
// Iterate over the input
var index = 0;
for (let item of iterable) {
processPromise(index, item);
index += 1;
total += 1;
}
// If the iterable was empty, resolve immediately
if (total === 0) {
resolve(results);
}
});
};
}-
Check if
Promise.allalready exists: The polyfill checks ifPromise.allis not defined to avoid overwriting the native implementation if it exists. -
Create a new promise: The polyfill function returns a new
Promise. -
Check if the input is iterable: It verifies whether the provided argument is an iterable. If it's not, it rejects the promise with a
TypeError. -
Initialize variables:
results: An array to hold the results of the resolved promises.completed: A counter to track the number of resolved promises.total: The total number of promises in the iterable.hasCalledReject: A flag to ensure the promise rejects only once if any input promise rejects.
-
Process each promise: A helper function
processPromiseis defined to handle each promise in the iterable:- It converts each value to a promise using
Promise.resolve. - On successful resolution, it stores the result in the
resultsarray and increments thecompletedcounter. - If all promises resolve, it resolves the returned promise with the
resultsarray. - If any promise rejects, it rejects the returned promise with the encountered error.
- It converts each value to a promise using
-
Iterate over the input: The function iterates over the provided iterable, processing each item with
processPromise. -
Handle empty iterables: If the iterable is empty (i.e.,
totalis 0), it resolves the returned promise immediately with an emptyresultsarray.
- This polyfill ensures compatibility with environments where
Promise.allis not available, providing a reliable implementation of the method.
In JavaScript interviews, candidates might be asked to write polyfills for various methods and functionalities that are commonly used in modern JavaScript but may not be available in older environments. Here are some of the most common polyfills you might be asked to implement, along with their code examples:
The map method creates a new array populated with the results of calling a provided function on every element in the calling array.
if (!Array.prototype.map) {
Array.prototype.map = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.map called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
var O = Object(this);
var len = O.length >>> 0;
var T = thisArg || undefined;
var A = new Array(len);
for (var k = 0; k < len; k++) {
if (k in O) {
A[k] = callback.call(T, O[k], k, O);
}
}
return A;
};
}The filter method creates a new array with all elements that pass the test implemented by the provided function.
if (!Array.prototype.filter) {
Array.prototype.filter = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.filter called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
var O = Object(this);
var len = O.length >>> 0;
var T = thisArg || undefined;
var A = [];
for (var k = 0; k < len; k++) {
if (k in O) {
var kValue = O[k];
if (callback.call(T, kValue, k, O)) {
A.push(kValue);
}
}
}
return A;
};
}The reduce method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
if (!Array.prototype.reduce) {
Array.prototype.reduce = function (callback, initialValue) {
if (this == null) {
throw new TypeError("Array.prototype.reduce called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
var O = Object(this);
var len = O.length >>> 0;
var k = 0;
var accumulator;
if (arguments.length >= 2) {
accumulator = initialValue;
} else {
while (k < len && !(k in O)) {
k++;
}
if (k >= len) {
throw new TypeError("Reduce of empty array with no initial value");
}
accumulator = O[k++];
}
while (k < len) {
if (k in O) {
accumulator = callback(accumulator, O[k], k, O);
}
k++;
}
return accumulator;
};
}The bind method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1);
var fToBind = this;
var fNOP = function () {};
var fBound = function () {
return fToBind.apply(
this instanceof fNOP ? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments))
);
};
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}The Object.create method creates a new object, using an existing object as the prototype of the newly created object.
if (typeof Object.create !== "function") {
Object.create = function (prototype, propertiesObject) {
if (prototype !== Object(prototype) && prototype !== null) {
throw new TypeError("Object prototype may only be an Object or null");
}
function F() {}
F.prototype = prototype;
var obj = new F();
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject);
}
if (prototype === null) {
obj.__proto__ = null;
}
return obj;
};
}The isArray method determines whether the passed value is an Array.
if (!Array.isArray) {
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
}The trim method removes whitespace from both ends of a string.
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, "");
};
}Implementing a full polyfill for Promise can be quite extensive, but here is a basic polyfill for Promise:
(function (global) {
if (typeof global.Promise !== "function") {
global.Promise = function (executor) {
if (typeof executor !== "function") {
throw new TypeError("Promise resolver must be a function");
}
var state = "pending";
var value;
var handlers = [];
function resolve(result) {
if (state !== "pending") return;
state = "fulfilled";
value = result;
handlers.forEach(handle);
}
function reject(error) {
if (state !== "pending") return;
state = "rejected";
value = error;
handlers.forEach(handle);
}
function handle(handler) {
if (state === "fulfilled") {
handler.onFulfilled(value);
} else if (state === "rejected") {
handler.onRejected(value);
} else {
handlers.push(handler);
}
}
this.then = function (onFulfilled, onRejected) {
return new global.Promise(function (resolve, reject) {
handle({
onFulfilled: function (result) {
if (typeof onFulfilled === "function") {
try {
resolve(onFulfilled(result));
} catch (e) {
reject(e);
}
} else {
resolve(result);
}
},
onRejected: function (error) {
if (typeof onRejected === "function") {
try {
resolve(onRejected(error));
} catch (e) {
reject(e);
}
} else {
reject(error);
}
},
});
});
};
executor(resolve, reject);
};
}
})(this);const arr = [1, 3, 4, 5, 5, 6];
const logger = (item, index) => {
console.log(item, index);
};
Array.prototype.forEach = function (callBack) {
for (let i = 0; i < this.length; i++) {
callBack(this[i], i);
}
};
arr.forEach(logger);const arr = [1, 2, 3, 4, 5, [6, 4, 3, 5, [1, 2, 3]]];
function myFlat(arr, depth = 1, output = []) {
if (depth <= 0) {
output.push(arr);
return output;
} else {
for (const item of arr) {
if (Array.isArray(item)) {
myFlat(item, depth - 1, output);
} else output.push(item);
}
}
return output;
}
console.log(myFlat(arr, 10));