37

By default the Promise.All([]) function returns a number based index array that contains the results of each promise.

var promises = [];
promises.push(myFuncAsync1()); //returns 1
promises.push(myFuncAsync1()); //returns 2
Promise.all(promises).then((results)=>{
    //results = [0,1]
}

What is the best vanilla way to return a named index of results with Promise.all()?

I tried with a Map, but it returns results in an array this way: [key1, value1, key2, value2]

UPDATE:

My questions seems unclear, here is why i don't like ordered based index:

  1. it's crappy to maintain: if you add a promise in your code you may have to rewrite the whole results function because the index may have change.
  2. it's awful to read: results[42] (can be fixed with jib's answer below)
  3. Not really usable in a dynamic context:
var promises = [];
if(...)
    promises.push(...);
else{
    [...].forEach(... => { 
        if(...)
            promises.push(...);
        else
            [...].forEach(... => {
                promises.push(...);
            });
    });
}
Promise.all(promises).then((resultsArr)=>{
    /*Here i am basically fucked without clear named results 
        that dont rely on promises' ordering in the array */
});
Anthony Bobenrieth
  • 2,758
  • 1
  • 26
  • 38
  • I don't think you can, or should? By "named" I assume you mean an object with keys and values, and what would those keys be, and what do you expect to get ? – adeneo Feb 09 '16 at 16:44
  • Many libraries like RSVP implement a hash method. You may need another array like ['myFuncAsync1', 'myFuncAsync2']. And then map on the results like Promise.all(promises).then((results)=> { var temp = {}; results.forEach((result, index) => { temp[promisesNames[index]] = result; }); return temp; }.then(result => {}) – blessanm86 Feb 09 '16 at 16:50
  • 1
    Too bad it's not like jQuery promises in which you can do $.when( deffered1, deffered2 ).done(function ( result1, result2 ) {...}) – phenxd Feb 09 '16 at 16:51
  • By what do you want to "name" your results (or their indices)? Should it happen in the callback or for the promises? Does it need to by dynamic? – Bergi Feb 09 '16 at 16:52
  • @phenxd - it's exactly like that, `Promise.all([ promise1, promise2 ]).then(function(result) { [result1, result2] });` etc. – adeneo Feb 09 '16 at 18:45
  • Can you review you're accepted answer? The destructuring way seems like a much better answer than mine. – spender Feb 11 '16 at 16:19
  • 1
    @spender It looks nicer, but that exactly the same probleme as the default index: we are using an index based on promises ordering to get results. My questions seems not clear enough i ll update it. Sorry. – Anthony Bobenrieth Feb 11 '16 at 18:04

5 Answers5

85

ES6 supports destructuring, so if you just want to name the results you can write:

var myFuncAsync1 = () => Promise.resolve(1);
var myFuncAsync2 = () => Promise.resolve(2);

Promise.all([myFuncAsync1(), myFuncAsync2()])
  .then(([result1, result2]) => console.log(result1 +" and "+ result2)) //1 and 2
  .catch(e => console.error(e));

Works in Firefox and Chrome now.

jib
  • 40,579
  • 17
  • 100
  • 158
  • 7
    I forgot about destructuring. This is the nifty way to do it and deserves to be the accepted answer. – spender Feb 11 '16 at 16:18
  • 2
    Whether you use indexes or destrcturing, either way it does not help when you have dozens of entries in `Promise.all()` sadly. – Klesun Jan 12 '20 at 10:52
  • 2
    @ArturKlesun That seems like a problem with the question, not the answer. I think it goes without saying here that if you want name-based results, you'll have to provide those names somehow, which may be inherently arduous for dozens, but should work the same nonetheless. `Promise.all` works on arrays, not objects. Of course, a clever person could write a wrapper to change that... – jib Jan 12 '20 at 14:07
17

Is this the kind of thing?

var promises = [];
promises.push(myFuncAsync1().then(r => ({name : "func1", result : r})));
promises.push(myFuncAsync1().then(r => ({name : "func2", result : r})));
Promise.all(promises).then(results => {
    var lookup = results.reduce((prev, curr) => {
        prev[curr.name] = curr.result;
        return prev;
    }, {});
    var firstResult = lookup["func1"];
    var secondResult = lookup["func2"];
}
spender
  • 117,338
  • 33
  • 229
  • 351
  • 1
    @vsync Indeed. Corrected, although the highly upvoted answer below is a much better approach in the modern JS world. – spender Jun 23 '20 at 10:02
7

If you don't want to modify the format of result objects, here is a helper function that allows assigning a name to each entry to access it later.

const allNamed = (nameToPromise) => {
    const entries = Object.entries(nameToPromise);
    return Promise.all(entries.map(e => e[1]))
        .then(results => {
            const nameToResult = {};
            for (let i = 0; i < results.length; ++i) {
                const name = entries[i][0];
                nameToResult[name] = results[i];
            }
            return nameToResult;
        });
};

Usage:

var lookup = await allNamed({
    rootStatus: fetch('https://stackoverflow.com/').then(rs => rs.status),
    badRouteStatus: fetch('https://stackoverflow.com/badRoute').then(rs => rs.status),
});

var firstResult = lookup.rootStatus; // = 200
var secondResult = lookup.badRouteStatus; // = 404

If you are using typescript you can even specify relationship between input keys and results using keyof construct:

type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;

export const allNamed = <
    T extends Record<string, Promise<any>>,
    TResolved extends {[P in keyof T]: ThenArg<T[P]>}
>(nameToPromise: T): Promise<TResolved> => {
    const entries = Object.entries(nameToPromise);
    return Promise.all(entries.map(e => e[1]))
        .then(results => {
            const nameToResult: TResolved = <any>{};
            for (let i = 0; i < results.length; ++i) {
                const name: keyof T = entries[i][0];
                nameToResult[name] = results[i];
            }
            return nameToResult;
        });
};

enter image description here

Klesun
  • 12,280
  • 5
  • 59
  • 52
  • The typescript version doesn't really work for me. What trying to create an object of promises with assignments, typescript compiler doesn't know which types to expect back (it just writes `any`) – Sagi Rika Oct 27 '21 at 07:30
0

A great solution for this is to use async await. Not exactly ES6 like you asked, but ES8! But since Babel supports it fully, here we go:

You can avoid using only the array index by using async/await as follows.

This async function allows you to literally halt your code inside of it by allowing you to use the await keyword inside of the function, placing it before a promise. As as an async function encounters await on a promise that hasn't yet been resolved, the function immediately returns a pending promise. This returned promise resolves as soon as the function actually finishes later on. The function will only resume when the previously awaited promise is resolved, during which it will resolve the entire await Promise statement to the return value of that Promise, allowing you to put it inside of a variable. This effectively allows you to halt your code without blocking the thread. It's a great way to handle asynchronous stuff in JavaScript in general, because it makes your code more chronological and therefore easier to reason about:

async function resolvePromiseObject(promiseObject) {
    await Promise.all(Object.values(promiseObject));

    const ret = {};

    for ([key, value] of Object.entries(promiseObject)) {
        // All these resolve instantly due to the previous await
        ret[key] = await value;
    };

    return ret;
}

As with anything above ES5: Please make sure that Babel is configured correctly so that users on older browsers can run your code without issue. You can make async await work flawlessly on even IE11, as long as your babel configuration is right.

kragovip
  • 426
  • 5
  • 11
  • 1
    No, **[don't do that](https://stackoverflow.com/questions/46889290/waiting-for-more-than-one-concurrent-await-operation)**! Use `Promise.all` – Bergi Sep 12 '18 at 21:47
  • @Bergi That's actually a good point with regards to the error handling. It's just a one-line fix though. Would've been nice if you were more descriptive with your answer. You can still get neatly named resolved Promises with this method. – kragovip Sep 12 '18 at 23:49
0

in regards to @kragovip's answer, the reason you want to avoid that is shown here:

https://medium.com/front-end-weekly/async-await-is-not-about-making-asynchronous-code-synchronous-ba5937a0c11e

"...it’s really easy to get used to await all of your network and I/O calls.

However, you should be careful when using it multiple times in a row as the await keyword stops execution of all the code after it. (Exactly as it would be in synchronous code)"

Bad Example (DONT FOLLOW)

async function processData() {
  const data1 = await downloadFromService1();
  const data2 = await downloadFromService2();
  const data3 = await downloadFromService3();

  ...
}

"There is also absolutely no need to wait for the completion of first request as none of other requests depend on its result.

We would like to have requests sent in parallel and wait for all of them to finish simultaneously. This is where the power of asynchronous event-driven programming lies.

To fix this we can use Promise.all() method. We save Promises from async function calls to variables, combine them to an array and await them all at once."

Instead

async function processData() {
  const promise1 = downloadFromService1();
  const promise2 = downloadFromService2();
  const promise3 = downloadFromService3();

  const allResults = await Promise.all([promise1, promise2, promise3]);
fballer87
  • 74
  • 9