最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Does Promise.all run the promises in Parallel? - Stack Overflow

programmeradmin3浏览0评论

I've seen many people saying that Promise.all can't achieve parallelism, since node/javascript runs on a single-threaded environment. However, if i, for instance, wrap 5 promises inside a Promise.all, in which every single one of the promises resolves after 3 seconds (a simple setTimeout promise), how e Promise.all resolves all of them in 3 seconds instead of something like 15 seconds (5 x 3 sec each)?

See the example below:

function await3seconds () { 
    return new Promise(function(res) {
        setTimeout(() => res(), 3000)
    })
}

console.time("Promise.all finished in::")

Promise.all([await3seconds(), await3seconds(), await3seconds(), await3seconds(), await3seconds()])
    .then(() => {
        console.timeEnd("Promise.all finished in::")
})

It logs:

Promise.all finished in::: 3.016s

How is this behavior possible without parallelism? Concurrent execution wouldn't be able to proccess all of these promises in 3 seconds either.

I've seen many people saying that Promise.all can't achieve parallelism, since node/javascript runs on a single-threaded environment. However, if i, for instance, wrap 5 promises inside a Promise.all, in which every single one of the promises resolves after 3 seconds (a simple setTimeout promise), how e Promise.all resolves all of them in 3 seconds instead of something like 15 seconds (5 x 3 sec each)?

See the example below:

function await3seconds () { 
    return new Promise(function(res) {
        setTimeout(() => res(), 3000)
    })
}

console.time("Promise.all finished in::")

Promise.all([await3seconds(), await3seconds(), await3seconds(), await3seconds(), await3seconds()])
    .then(() => {
        console.timeEnd("Promise.all finished in::")
})

It logs:

Promise.all finished in::: 3.016s

How is this behavior possible without parallelism? Concurrent execution wouldn't be able to proccess all of these promises in 3 seconds either.

Share Improve this question asked May 25, 2021 at 23:15 Raphael AlmeidaRaphael Almeida 311 silver badge2 bronze badges 11
  • 2 Because setTimeout doesn't block, it just queues a task after the timeout is done, which takes no real resources to execute – CertainPerformance Commented May 25, 2021 at 23:16
  • 2 First of all, Promise.all doesn't run anything. It just waits. It's the 5 independent await3seconds() calls that don't wait for each other, scheduling their timers at once, what makes things concurrent. – Bergi Commented May 25, 2021 at 23:16
  • 2 Concurrency (multiple timers active at the same time) is not the same thing as (multi-threading) parallelism. – Bergi Commented May 25, 2021 at 23:17
  • I don't understand what you mean by "Concurrent execution wouldn't be able to proccess all of these promises in 3 seconds either." – Bergi Commented May 25, 2021 at 23:17
  • 1 @RaphaelAlmeida No difference between timers and API calls in that regard. Network I/O is asynchronous. It sends the requests all at once, then when the responses arrive the OS notifies the runtime which schedules a task to resolve the respective promise and run its handlers. – Bergi Commented May 25, 2021 at 23:30
 |  Show 6 more ments

3 Answers 3

Reset to default 8

It's particularly useful to understand what this line of code actually does:

Promise.all([await3seconds(), await3seconds(), await3seconds(), await3seconds(), await3seconds()]).then(...)

That is fundamentally the same as:

const p1 = await3seconds();
const p2 = await3seconds();
const p3 = await3seconds();
const p4 = await3seconds();
const p5 = await3seconds();

Promise.all([p1, p2, p3, p4, p5]).then(...)

What I'm trying to show here is that ALL your functions are executed serially one after the other in the order declared and they have all returned BEFORE Promise.all() is even executed.

So, some conclusions from that:

  1. Promise.all() didn't "run" anything. It accepts an array of promises and it justs monitors all those promises, collects their results in order and notifies you (via .then() or await) when they are all done or tells you when the first one rejects.
  2. Your functions are already executed and have returned a promise BEFORE Promise.all() even runs. So, Promise.all() doesn't determine anything about how those functions run.
  3. If the functions you were calling were blocking, the first would run to pletion before the second was even called. Again, this has nothing to do with Promise.all() before the functions are all executed before Promise.all() is even called.
  4. In your particular example, your functions each start a timer and immediately return. So, you essentially start 5 timers within ms of each other that are all set to fire in 3 seconds. setTimeout() is non-blocking. It tells the system to create a timer and gives the system a callback to call when that timer fires and then IMMEDIATELY returns. Sometime later, when the event loop is free, the timer will fire and the callback will get called. So, that's why all the timers are set at once and all fire at about the same time. If you wanted them to each be spread out by 3 seconds apart, you'd have to write this code differently, either to set increasing times for each timer or to not start the 2nd timer until the first one fires and so on.

So, what Promise.all() allows you to do is to monitor multiple asynchronous operations that are, by themselves (independent of Promise.all()) capable of running asynchronously. Nodejs itself, nothing to do with Promise.all(), has the ability to run multiple asynchronous operations in parallel. For example, you can make multiple http requests or make multiple read requests from the file system and nodejs will run those in parallel. They will all be "in flight" at the same time.

So, Promise.all() isn't enabling parallelism of asynchronous operations. That capability is built into the asynchronous operations themselves and how they interact with nodejs and how they are implemented. Promise.all() allows you to track multiple asynchronous operations and know when they are all done, get their results in order and/or know when there was an error in one of the operations.


If you're curious how timers work in nodejs, they are managed by libuv which is a cross platform library that nodejs uses for managing the event loop, timers, file system access, networking and a whole bunch of things.

Inside of libuv, it manages a sorted list of pending timers. The timers are sorted by their next time to fire so the soonest timer to fire is at the start of the list.

The event loop within nodejs goes in a cycle to check for a bunch of different things and one of those things is to see if the current head of the timer list has reached its firing time. If so, it removes that timer from the list, grabs the callback associated with that timer and calls it.

Other types of asynchronous operations such as file system access work pletely differently. The asynchronous file operations in the fs module, actually use a native code thread pool. So, when you request an asynchronous file operation, it actually grabs a thread from the thread pool, gives it the job for the particular file operation you requested and sends the thread on its way. That native code thread, then runs independently from the Javascript interpreter which is free to go do other things. At some future time when the thread finishes the file operation, it calls a thread safe interface of the event loop to add a file pletion event to a queue. When whatever Javascript is currently executing finishes and returns control back to the event loop, one of the things the event loop will do is check if there are any file system pletion events waiting to be executed. If so, it will remove it from the queue and call the callback associated with it.

So, while individual asynchronous operation can themselves run in parallel if designed appropriately (usually backed by native code), signaling pletion or errors to your Javascript all runs through the event loop and the interpreter is only running one piece of Javascript at a time.

Note: this is ignoring for the purposes of this discussion, the WorkerThread capability which actually fires up a whole different interpreter and can run multiple sets of Javascript at the same time. But, each individual interpreter still runs your Javascript single threaded and is still coordinated with the outside world through the event loop.

First, Promise.all has nothing to do with JS code running in parallel. You can think of Promise.all as code organizer, it puts code together and wait for response. What's responsible for the code to run in a way that "looks like" it's parallel is the event-based nature of JS. So in your case:

Promise.all([await3seconds(), await3seconds(), 
await3seconds(), await3seconds(), await3seconds()])
.then(() => {
    console.timeEnd("Promise.all finished in::")
})

Let's say that each function inside Promise.all is called a1 : a5, What will happen is:

  1. The Event loop will take a1 : a5 and put them in the "Callback Queue/Task Queue" sequentially (one after the other), But it will not take too much time, because it's just putting it in there, not executing anything.
  2. The timer will start immediately after each function is put by the Event Loop in the "Callback Queue/Task Queue" (so there will be a minor delay between the start of each one).
  3. Whenever a timer finishes, the Event loop will take the related callback function and put it in the "Call Stack" to be executed.
  4. Promise.all will resolve after the last function is popped out of the "Call Stack".

As you can see in here

Promise.all finished in::: 3.016s

The 0.16s delay is a bination between the time the Event loop took to put those callback functions sequentially in the "Callback Queue/Task Queue" + the time each function took to execute the code inside it after their timer has finished.

So the code is not executed in parallel, it's still sequential, but the event-based nature of JS is used to mimic the behavior of parallelism.

Look at this article to relate more to what I am trying to say.

Synchronous vs Asynchronous JavaScript

No they are not executed in parallel but you can conceptualize them this way. This is just how the event queue works. If each promise contained a heavy pute task, they would still be executed one at a time -

function await3seconds(id) { 
    return new Promise(res => {
        console.log(id, Date.now())
        setTimeout(_=> {
          console.log(id, Date.now())
          res()
        }, 3000)
    })
}

console.time("Promise.all finished in::")

Promise.all([await3seconds(1), await3seconds(2), await3seconds(3), await3seconds(4), await3seconds(5)])
    .then(() => {
        console.timeEnd("Promise.all finished in::")
})

time 1 2 3 4 5
1621997095724 start
1621997095725 start
1621997095725 start
1621997095725 start
1621997095726 start
1621997098738 end
1621997098740 end
1621997098740 end
1621997098741 end
1621997098742 end

In this related Q&A we build a batch processing Pool that emulates threads. Check it out if that kind of thing interests you!

发布评论

评论列表(0)

  1. 暂无评论