613
votes

As far as I understand, in ES7/ES2016 putting multiple await's in code will work similar to chaining .then() with promises, meaning that they will execute one after the other rather than in parallel. So, for example, we have this code:

await someCall();
await anotherCall();

Do I understand it correctly that anotherCall() will be called only when someCall() is completed? What is the most elegant way of calling them in parallel?

I want to use it in Node, so maybe there's a solution with async library?

EDIT: I'm not satisfied with the solution provided in this question: Slowdown due to non-parallel awaiting of promises in async generators, because it uses generators and I'm asking about a more general use case.

11
@Blindman67 - it does, at least the way the OP means, where two async operations are running simultaneously, but not in this case, what I meant to write was that they run in serial, the first await would wait for the first function to complete entirely before executing the second.adeneo
@Blindman67 - it's single threaded, but that limitation doesn't apply to async methods, they can run simultaneously, and return the response when they are done, i.e. what the OP means by "parallell".adeneo
@Blindman67 - I think it's pretty clear what the OP is asking, using the async/await pattern will make the functions run in serial, even if they are async, so the first would completely finish before the second is called etc. The OP is asking how to call both function in parallell, and as they are clearly async, the aim is to run them simultaneously, i.e. in parallell, for instance doing two ajax requests simultaneously, which is not a problem at all in javascript, as most async methods, as you've noted, runs native code, and uses more threads.adeneo
@Bergi this is not a duplicate of the linked question — this is specifically about async/await syntax and native Promises. The linked question is regarding the bluebird library with generators & yield. Conceptually similar perhaps, but not in implementation.Iest
@Bergi The syntax very much does matter. To a person that has never used generators or bluebird the linked question is completely unhelpful.Iest

11 Answers

981
votes

You can await on Promise.all():

await Promise.all([someCall(), anotherCall()]);

To store the results:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Note that Promise.all fails fast, which means that as soon as one of the promises supplied to it rejects, then the entire thing rejects.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
  .then(console.log).catch(console.log) // 'sad'

If, instead, you want to wait for all the promises to either fulfill or reject, then you can use Promise.allSettled. Note that Internet Explorer does not natively support this method.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.allSettled([happy('happy', 100), sad('sad', 50)])
  .then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]

Note: If you use Promise.all actions that managed to finish before rejection happen are not rolled back, so you may need to take care of such situation. For example if you have 5 actions, 4 quick, 1 slow and slow rejects. Those 4 actions may be already executed so you may need to roll back. In such situation consider using Promise.allSettled while it will provide exact detail which action failed and which not.

131
votes

TL;DR

Use Promise.all for the parallel function calls, the answer behaviors not correctly when the error occurs.


First, execute all the asynchronous calls at once and obtain all the Promise objects. Second, use await on the Promise objects. This way, while you wait for the first Promise to resolve the other asynchronous calls are still progressing. Overall, you will only wait for as long as the slowest asynchronous call. For example:

// Begin first call and store promise without waiting
const someResult = someCall();

// Begin second call and store promise without waiting
const anotherResult = anotherCall();

// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];

// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise

JSbin example: http://jsbin.com/xerifanima/edit?js,console

Caveat: It doesn't matter if the await calls are on the same line or on different lines, so long as the first await call happens after all of the asynchronous calls. See JohnnyHK's comment.


Update: this answer has a different timing in error handling according to the @bergi's answer, it does NOT throw out the error as the error occurs but after all the promises are executed. I compare the result with @jonny's tip: [result1, result2] = Promise.all([async1(), async2()]), check the following code snippet

const correctAsync500ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, 'correct500msResult');
  });
};

const correctAsync100ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, 'correct100msResult');
  });
};

const rejectAsync100ms = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'reject100msError');
  });
};

const asyncInArray = async (fun1, fun2) => {
  const label = 'test async functions in array';
  try {
    console.time(label);
    const p1 = fun1();
    const p2 = fun2();
    const result = [await p1, await p2];
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

const asyncInPromiseAll = async (fun1, fun2) => {
  const label = 'test async functions with Promise.all';
  try {
    console.time(label);
    let [value1, value2] = await Promise.all([fun1(), fun2()]);
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

(async () => {
  console.group('async functions without error');
  console.log('async functions without error: start')
  await asyncInArray(correctAsync500ms, correctAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
  console.groupEnd();

  console.group('async functions with error');
  console.log('async functions with error: start')
  await asyncInArray(correctAsync500ms, rejectAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
  console.groupEnd();
})();
106
votes

Update:

The original answer makes it difficult (and in some cases impossible) to correctly handle promise rejections. The correct solution is to use Promise.all:

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Original answer:

Just make sure you call both functions before you await either one:

// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;
37
votes

There is another way without Promise.all() to do it in parallel:

First, we have 2 functions to print numbers:

function printNumber1() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number1 is done");
      resolve(10);
      },1000);
   });
}

function printNumber2() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number2 is done");
      resolve(20);
      },500);
   });
}

This is sequential:

async function oneByOne() {
   const number1 = await printNumber1();
   const number2 = await printNumber2();
} 
//Output: Number1 is done, Number2 is done

This is parallel:

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
12
votes

I've created a gist testing some different ways of resolving promises, with results. It may be helpful to see the options that work.

Edit: Gist content as per Jin Lee's comment

// Simple gist to test parallel promise resolution when using async / await

function promiseWait(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(true);
    }, time);
});
}


async function test() {
    return [
    await promiseWait(1000),
    await promiseWait(5000),
    await promiseWait(9000),
    await promiseWait(3000),
    ]
}

async function test2() {
    return {
        'aa': await promiseWait(1000),
        'bb': await promiseWait(5000),
        'cc': await promiseWait(9000),
        'dd': await promiseWait(3000),
    }
}

async function test3() {
    return await {
        'aa': promiseWait(1000),
        'bb': promiseWait(5000),
        'cc': promiseWait(9000),
        'dd': promiseWait(3000),
    }
}

async function test4() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    const p4 =  promiseWait(3000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await p4,
    };
}

async function test5() {
    return await Promise.all([
                             await promiseWait(1000),
                             await promiseWait(5000),
                             await promiseWait(9000),
                             await promiseWait(3000),
                             ]);
}

async function test6() {
    return await Promise.all([
                             promiseWait(1000),
                             promiseWait(5000),
                             promiseWait(9000),
                             promiseWait(3000),
                             ]);
}

async function test7() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await promiseWait(3000),
    };
}

let start = Date.now();

test().then((res) => {
    console.log('Test Done, elapsed', (Date.now() - start) / 1000, res);

    start = Date.now();
    test2().then((res) => {
        console.log('Test2 Done, elapsed', (Date.now() - start) / 1000, res);

        start = Date.now();
        test3().then((res) => {
            console.log('Test3 Done, elapsed', (Date.now() - start) / 1000, res);

            start = Date.now();
            test4().then((res) => {
                console.log('Test4 Done, elapsed', (Date.now() - start) / 1000, res);

                start = Date.now();
                test5().then((res) => {
                    console.log('Test5 Done, elapsed', (Date.now() - start) / 1000, res);

                    start = Date.now();
                    test6().then((res) => {
                        console.log('Test6 Done, elapsed', (Date.now() - start) / 1000, res);
                    });

                    start = Date.now();
                    test7().then((res) => {
                        console.log('Test7 Done, elapsed', (Date.now() - start) / 1000, res);
                    });
                });
            });

        });
    });

});
/*
Test Done, elapsed 18.006 [ true, true, true, true ]
Test2 Done, elapsed 18.009 { aa: true, bb: true, cc: true, dd: true }
Test3 Done, elapsed 0 { aa: Promise { <pending> },
  bb: Promise { <pending> },
  cc: Promise { <pending> },
  dd: Promise { <pending> } }
Test4 Done, elapsed 9 { aa: true, bb: true, cc: true, dd: true }
Test5 Done, elapsed 18.008 [ true, true, true, true ]
Test6 Done, elapsed 9.003 [ true, true, true, true ]
Test7 Done, elapsed 12.007 { aa: true, bb: true, cc: true, dd: true }
*/
8
votes

In my case, I have several tasks I want to execute in parallel, but I need to do something different with the result of those tasks.

function wait(ms, data) {
    console.log('Starting task:', data, ms);
    return new Promise(resolve => setTimeout(resolve, ms, data));
}

var tasks = [
    async () => {
        var result = await wait(1000, 'moose');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(500, 'taco');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(5000, 'burp');
        // do something with result
        console.log(result);
    }
]

await Promise.all(tasks.map(p => p()));
console.log('done');

And the output:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done
4
votes

await Promise.all([someCall(), anotherCall()]); as already mention will act as a thread fence (very common in parallel code as CUDA), hence it will allow all the promises in it to run without blocking each other, but will prevent the execution to continue until ALL are resolved.

another approach that is worth to share is the Node.js async that will also allow you to easily control the amount of concurrency that is usually desirable if the task is directly linked to the use of limited resources as API call, I/O operations, etc.

// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
  console.log('Hello ' + task.name);
  callback();
}, 2);

// assign a callback
q.drain = function() {
  console.log('All items have been processed');
};

// add some items to the queue
q.push({name: 'foo'}, function(err) {
  console.log('Finished processing foo');
});

q.push({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

// add some items to the queue (batch-wise)
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
  console.log('Finished processing item');
});

// add some items to the front of the queue
q.unshift({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

Credits to the Medium article autor (read more)

3
votes
    // A generic test function that can be configured 
    // with an arbitrary delay and to either resolve or reject
    const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
        console.log(`Done ${ delay }`);
        resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
    }, delay));

    // Our async handler function
    const handler = async () => {
        // Promise 1 runs first, but resolves last
        const p1 = test(10000, true);
        // Promise 2 run second, and also resolves
        const p2 = test(5000, true);
        // Promise 3 runs last, but completes first (with a rejection) 
        // Note the catch to trap the error immediately
        const p3 = test(1000, false).catch(e => console.log(e));
        // Await all in parallel
        const r = await Promise.all([p1, p2, p3]);
        // Display the results
        console.log(r);
    };

    // Run the handler
    handler();
    /*
    Done 1000
    Reject 1000
    Done 5000
    Done 10000
    */

Whilst setting p1, p2 and p3 is not strictly running them in parallel, they do not hold up any execution and you can trap contextual errors with a catch.

0
votes

For Readers in 2021 who needs Front-end New Stars's way:

The following is the most beautiful front-end API gateway code ever exist, it calls three different services in parallel, then use one of the result to loop-call anther service, ProblemService.

Notice that how I use await, async, and Promise.all that beats entire StackOverflow.

class ExamScoreboardService {
    getExamScoreboard(examId) {
        return Promise.all([
            examService.getExaminees(examId),
            examService.getExamOverview(examId),
            examService.getExamScores(examId),
        ])
            .then(async ([examinees, exam, examScores]) => {
                const out = {}
                await Promise.all(exam.questions.map(async q =>
                    problemService.getProblemById(q.problemId).then(problem => out[q.problemId] = problem.testcases.length)))
                return [examinees, exam, examScores, out]
            })
            .then(values => {
                const [examinees, exam, examScores, totalTestcasesOf] = values;
                return new ExamScoreboard({examinees, exam, examScores, totalTestcasesOf});
            })
    }
}
-5
votes

I create a helper function waitAll, may be it can make it sweeter. It only works in nodejs for now, not in browser chrome.

    //const parallel = async (...items) => {
    const waitAll = async (...items) => {
        //this function does start execution the functions
        //the execution has been started before running this code here
        //instead it collects of the result of execution of the functions

        const temp = [];
        for (const item of items) {
            //this is not
            //temp.push(await item())
            //it does wait for the result in series (not in parallel), but
            //it doesn't affect the parallel execution of those functions
            //because they haven started earlier
            temp.push(await item);
        }
        return temp;
    };

    //the async functions are executed in parallel before passed
    //in the waitAll function

    //const finalResult = await waitAll(someResult(), anotherResult());
    //const finalResult = await parallel(someResult(), anotherResult());
    //or
    const [result1, result2] = await waitAll(someResult(), anotherResult());
    //const [result1, result2] = await parallel(someResult(), anotherResult());

-6
votes

I vote for:

await Promise.all([someCall(), anotherCall()]);

Be aware of the moment you call functions, it may cause unexpected result:

// Supposing anotherCall() will trigger a request to create a new User

if (callFirst) {
  await someCall();
} else {
  await Promise.all([someCall(), anotherCall()]); // --> create new User here
}

But following always triggers request to create new User

// Supposing anotherCall() will trigger a request to create a new User

const someResult = someCall();
const anotherResult = anotherCall(); // ->> This always creates new User

if (callFirst) {
  await someCall();
} else {
  const finalResult = [await someResult, await anotherResult]
}