Building a Custom Promise.all from Scratch
How Promise.all Works
Promise.all takes an array of Promise instances and returns a new Promise. When all input promises resolve successfully, the returned promise resolves with an array of results in the same order as the inputs. If any single promise rejects, the entire operation fails immediately, returning that rejection reason.
Basic Behavier Example
const p1 = Promise.resolve('first');
const p2 = Promise.resolve('second');
const p3 = Promise.reject('failed');
// Successful case - resolves with result array
Promise.all([p1, p2]).then(results => {
console.log(results); // ['first', 'second']
});
// Failed case - immediately rejects with first rejection
Promise.all([p1, p3, p2]).catch(error => {
console.log(error); // 'failed'
});
Implementing Promise.all
Here's a custom implementation that captures the core behavior:
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const outputs = [];
const totalPromises = promises.length;
let completedCount = 0;
if (totalPromises === 0) {
resolve(outputs);
return;
}
promises.forEach((item, index) => {
if (item && typeof item.then === 'function') {
item.then(
value => {
outputs[index] = value;
completedCount++;
if (completedCount === totalPromises) {
resolve(outputs);
}
},
reason => {
reject(reason);
}
);
} else {
outputs[index] = item;
completedCount++;
if (completedCount === totalPromises) {
resolve(outputs);
}
}
});
});
}
Testing the Implementation
const promiseA = Promise.resolve(42);
const promiseB = new Promise((_, reject) => setTimeout(() => reject('error'), 100));
const promiseC = Promise.resolve(99);
promiseAll([promiseA, promiseC]).then(values => {
console.log(values); // [42, 99]
});
promiseAll([promiseA, promiseB, promiseC]).catch(err => {
console.log(err); // 'error'
});
promiseAll([]).then(values => {
console.log(values); // []
});
Key Implementation Details
Input Validation: The function checks if each item has a .then method to determine if it's a Promise. Non-Promise values are handled by wrapping them directly into the result array.
Short-Circuit on Rejection: Using the second argument of .then() for rejection hnadling ensures immediate rejection when any Promise fails.
Order Preservation: Results are assigned using the original array index, maintaining input order regardless of resolution timing.
Empty Array Handling: An edge case check returns an immediately resolved Promise with an empty array when no inputs are prvoided.
Counter Mechanism: A completion counter tracks resolved promises and triggers the final resolution only when all promises have been processed.