The contract to meet:
- Accept any iterable, not only arrays. Items can be promises, thenables or plain values.
- Return a promise that fulfills with an array of values in input order, regardless of completion order.
- Reject with the first rejection reason.
- Fulfill with
[]for an empty input. - A non-iterable or a throwing iterator makes the returned promise reject. It must not throw synchronously.
The approach:
- Create the result with
new Promise. Its executor already turns a synchronous throw, such asfor…ofon a non-iterable, into a rejection. - Loop with
for…of, remember each item'sindex, and wrap the item withPromise.resolve(item)so plain values and thenables work. - Attach
then(onValue, reject).onValuestores the value at its own index.rejectis the outerreject, so the first rejection wins and later calls are ignored. - Keep a
pendingcounter, incremented per item and decremented per fulfillment. Resolve when it hits zero. After the loop, ifpendingis still zero the input was empty, so resolve now.
Why not push? Values arrive in completion order. Writing to results[index] keeps input order. Why not results.length === total? An iterable has no length, and a sparse array's length can be reached early, so count completions instead.
What this skips compared to the spec: it always uses the global Promise (the real method uses its this as the constructor, so subclasses work), and it calls Promise.resolve directly rather than looking it up once.