Implement your own version of Promise.all. You take an iterable of promises (or plain values), kick them all off in parallel, and return a single promise that resolves with an array of their results — in input order — or rejects as soon as any of them rejects.
// Returns a Promise that fulfils with results[] in input order,
// or rejects with the reason of the first input to reject.
function promiseAll<T>(iterable: Iterable<T | PromiseLike<T>>): Promise<T[]>;
promiseAll([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
.then(console.log); // [1, 2, 3]
// Non-promise values pass through. Order is preserved regardless of timing.
const slow = new Promise((r) => setTimeout(() => r('slow'), 30));
const fast = Promise.resolve('fast');
promiseAll([slow, fast, 42]).then(console.log); // ['slow', 'fast', 42]
// First rejection wins. The 'b' that resolves later is discarded.
const ok = new Promise((r) => setTimeout(() => r('a'), 50));
const bad = Promise.reject('boom');
promiseAll([ok, bad]).catch(console.log); // 'boom'
// Empty iterable resolves to [] on the next microtask.
promiseAll([]).then(console.log); // []
results[i] must correspond to the i-th input, even if that input was the slowest to settle. Don't .push() results as they arrive.resolve or reject twice.42, 'hello', null, plain objects, and thenables (any object with a .then method) are all valid inputs. Wrap them with Promise.resolve so the same code path handles every case.for...of is the right shape.next() throws (or Symbol.iterator itself does), the returned promise rejects with that error rather than throwing synchronously to the caller.instanceof Promise.Promise.all to implement promiseAll. That defeats the exercise. You may use new Promise(...), Promise.resolve, and .then.