Skip to the code
cf.

Promise.all

Write promiseAll(iterable), which works like Promise.all without calling it. It returns a promise that waits for every input, then fulfills with an array of their values, in input order, not in the order they finish.

It rejects as soon as any input rejects, with that input's reason, without waiting for the rest. Inputs can be promises or plain values, and iterable can be any iterable. An empty iterable fulfills with []. If the argument isn't iterable, the returned promise rejects with a TypeError (calling promiseAll doesn't throw).

const [user, posts] = await promiseAll([fetchJson("/api/user"), fetchJson("/api/posts")]);
 
await promiseAll([1, Promise.resolve(2)]); // [1, 2]
await promiseAll([]); // []

What the tests check

  1. fulfills with the values in input order, not finish order
    const clock = useFakeTimers();
    const after = (ms, value) => new Promise((resolve) => setTimeout(resolve, ms, value));
    let result;
    promiseAll([after(300, "a"), after(100, "b"), after(200, "c")]).then((value) => {
      result = value;
    });
    await clock.tickAsync(300);
    expect(result).toEqual(["a", "b", "c"]);
    
  2. waits for every input before fulfilling
    const clock = useFakeTimers();
    const after = (ms, value) => new Promise((resolve) => setTimeout(resolve, ms, value));
    let result;
    promiseAll([after(300, 1), after(100, 2)]).then((value) => {
      result = value;
    });
    await clock.tickAsync(299);
    expect(result).toBeUndefined();
    await clock.tickAsync(1);
    expect(result).toEqual([1, 2]);
    
  3. accepts plain values
    let result;
    promiseAll([1, Promise.resolve(2), "three"]).then((value) => {
      result = value;
    });
    await flush();
    expect(result).toEqual([1, 2, "three"]);
    
  4. rejects as soon as one input rejects
    const clock = useFakeTimers();
    const error = new Error("posts failed");
    const slow = new Promise((resolve) => setTimeout(resolve, 1000, "user"));
    const failing = new Promise((_, reject) => setTimeout(reject, 100, error));
    failing.catch(() => {}); // mark it handled, so it's never reported as unhandled
    let reason;
    promiseAll([slow, failing]).catch((e) => {
      reason = e;
    });
    await clock.tickAsync(100);
    expect(reason).toBe(error);
    
  5. fulfills with [] for an empty iterable
    let result;
    promiseAll([]).then((value) => {
      result = value;
    });
    await flush();
    expect(result).toEqual([]);
    
  6. accepts any iterable, such as a generator
    function* inputs() {
      yield 1;
      yield Promise.resolve(2);
      yield 3;
    }
    let result;
    promiseAll(inputs()).then((value) => {
      result = value;
    });
    await flush();
    expect(result).toEqual([1, 2, 3]);
    
  7. rejects with a TypeError when the argument isn't iterable
    let promise;
    expect(() => {
      promise = promiseAll(42);
    }).not.toThrow();
    await expect(promise).rejects.toThrow(TypeError);
    
  8. doesn't call the built-in Promise.all
    const builtIn = Promise.all;
    Promise.all = () => {
      throw new Error("promiseAll called the built-in Promise.all");
    };
    let promise;
    try {
      promise = promiseAll([Promise.resolve("mine")]);
    } finally {
      Promise.all = builtIn;
    }
    await expect(promise).resolves.toEqual(["mine"]);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).