Cleaner way of using await Promise.all to get resolved objects

Viewed 135

Consider a bunch of promises - result1, result2, results3 etc.

I wait to await Promise.all and then work with the resolved objects. No further need for the promises.

I can use a destructuring assignment like so :-

[result1, result2, result3] = await Promise.all([result1, result2, result3]);

But there is repetition there ([result1, result2...] used twice) and its ripe for mistakes with a large number of promises.

Is there a better way?

1 Answers

You could have a utility function that used an object instead of an array, something along these lines:

async function allKeyed(promises) {
    // Get an array of [name, value] pairs for the object's properties
    const entries = Object.entries(promises);
    // Wait for any thenables/promises in the values to settle
    const values = await Promise.all(entries.map(([_, value]) => value));
    // Build an object from those result values; this works because the
    // array from `Promise.all` is in the same order as the array of
    // values we gave it above.
    const result = Object.fromEntries(entries.map(([key], index) => {
        return [key, values[index]];
    }));
    return result;
}

Then it would be something like:

const {a, b, c} = await allKeyed({
    a: promise1,
    b: promise2,
    c: promise3,
});

...but with meaningful names rather than a, b, and c. :-)

Live Example:

async function allKeyed(promises) {
    // Get an array of [name, value] pairs for the object's properties
    const entries = Object.entries(promises);
    // Wait for any thenables/promises in the values to settle
    const values = await Promise.all(entries.map(([_, value]) => value));
    // Build an object from those result values; this works because the
    // array from `Promise.all` is in the same order as the array of
    // values we gave it above.
    const result = Object.fromEntries(entries.map(([key], index) => {
        return [key, values[index]];
    }));
    return result;
}

function fetchSomething(value) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`fulfilling with ${value}`);
            resolve(value);
        }, Math.floor(Math.random() * 1000));
    });
}

(async () => {
    const {a, b, c} = await allKeyed({
        a: fetchSomething("ayy"),
        b: fetchSomething("bee"),
        c: fetchSomething("see"),
    });

    console.log({a, b, c});
})()
.catch(error => console.error(error));

In the question you were assigning the promises to variables before Promise.all using the same names for both the promises (to feed into Promise.all) and their fulfillment values (in the destructuring), which would look like this:

// ...assign promises to `resultX`, then:
({result1, result2, result3} = await allKeyed({result1, result2, result3}));

I have them in order there, but this would work just as well:

// ...assign promises to `resultX`, then:
({result3, result1, result2} = await allKeyed({result1, result2, result3}));

Live Example:

async function allKeyed(promises) {
    // Get an array of [name, value] pairs for the object's properties
    const entries = Object.entries(promises);
    // Wait for any thenables/promises in the values to settle
    const values = await Promise.all(entries.map(([_, value]) => value));
    // Build an object from those result values; this works because the
    // array from `Promise.all` is in the same order as the array of
    // values we gave it above.
    const result = Object.fromEntries(entries.map(([key], index) => {
        return [key, values[index]];
    }));
    return result;
}

function fetchSomething(value) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`fulfilling with ${value}`);
            resolve(value);
        }, Math.floor(Math.random() * 1000));
    });
}

(async () => {
    let result1 = fetchSomething("one");
    let result2 = fetchSomething("two");
    let result3 = fetchSomething("three");
    ({result3, result1, result2} = await allKeyed({result1, result2, result3}));

    console.log({result1, result2, result3});
})()
.catch(error => console.error(error));

Related