Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

await loop vs Promise.all [duplicate]

Having a set of async operations on db to do, I'm wondering what's the difference performance-wise of doing a "blocking" await loop versus a Promise.all.

let insert = (id,value) => {
    return new Promise(function (resolve, reject) {
        connnection.query(`insert into items (id,value) VALUES (${id},"${value}")`, function (err, result) {
            if (err) return reject(err)
                return resolve(result);
        });
    });
};

Promise.all solution (it needs a for loop to builds the array of promises..)

let inserts = [];
for (let i = 0; i < SIZE; i++) inserts.push(insert(i,"..string.."))
Promise.all(inserts).then(values => { 
    console.log("promise all ends");
});

await loop solution

let inserts = [];
(async function loop() {
    for (let i = 0; i < SIZE; i++) {
        await insert(i, "..string..")
    }
    console.log("await loop ends");
})

Edit: thanks for the anwsers, but I would dig into this a little more. await is not really blocking, we all know that, it's blocking in its own code block. An await loop sequentially fire requests, so if in the middle 1 requests takes longer, the other ones waits for it. Well this is similar to Promise.all: if a 1 req takes longer, the callback is not executed until ALL the responses are returned.

like image 271
alfredopacino Avatar asked Dec 16 '18 01:12

alfredopacino


1 Answers

Your example of using Promise.all will create all promises first before waiting for them to resolve. This means that your requests will fire concurrently and the callback given to Promise.all(...).then(thisCallback) will only fire if all requests were successful.

Note: promise returned from Promise.all will reject as soon as one of the promises in the given array rejects.

const SIZE = 5;
const insert = i => new Promise(resolve => {
  console.log(`started inserting ${i}`);
  setTimeout(() => {
    console.log(`inserted ${i}`);
    resolve();
  }, 300);
});

// your code
let inserts = [];
for (let i = 0; i < SIZE; i++) inserts.push(insert(i, "..string.."))
Promise.all(inserts).then(values => {
  console.log("promise all ends");
});

// requests are made concurrently

// output
// started inserting 0
// started inserting 1
// started inserting 2
// ...
// started inserting 4
// inserted 0
// inserted 1
// ...
// promise all ends

Note: It might be cleaner to use .map instead of a loop for this scenario:

Promise.all(
  Array.from(Array(SIZE)).map((_, i) => insert(i,"..string.."))
).then(values => { 
    console.log("promise all ends");
});

Your example of using await on the other hand, waits for each promise to resolve before continuing and firing of the next one:

const SIZE = 5;
const insert = i => new Promise(resolve => {
  console.log(`started inserting ${i}`);
  setTimeout(() => {
    console.log(`inserted ${i}`);
    resolve();
  }, 300);
});

let inserts = [];
(async function loop() {
  for (let i = 0; i < SIZE; i++) {
    await insert(i, "..string..")
  }
  console.log("await loop ends");
})()

// no request is made until the previous one is finished

// output
// started inserting 0
// inserted 0
// started inserting 1
// ...
// started inserting 4
// inserted 4
// await loop ends

The implications for performance in the above cases are directly correlated to their different behavior.

If "efficient" for your use case means to finish up the requests as soon as possible, then the first example wins because the requests will be happening around the same time, independently, whereas in the second example they will happen in a serial fashion.

In terms of complexity, the time complexity for your first example is equal to O(longestRequestTime) because the requests will happen essentially in parallel and thus the request taking the longest will drive the worst-case scenario.

On the other hand, the await example has O(sumOfAllRequestTimes) because no matter how long individual requests take, each one has to wait for the previous one to finish and thus the total time will always include all of them.

To put things in numbers, ignoring all other potential delays due to the environment and application in which the code is ran, for 1000 requests, each taking 1s, the Promise.all example would still take ~1s while the await example would take ~1000s.

Maybe a picture would help:

time comparison chart

Note: Promise.all won't actually run the requests exactly in parallel and the performance in general will greatly depend on the exact environment in which the code is running and the state of it (for instance the event loop) but this is a good approximation.

like image 108
nem035 Avatar answered Sep 27 '22 19:09

nem035