await loop vs Promise.all [duplicate]

假如想象 提交于 2020-05-26 04:51:29

问题


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.


回答1:


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:

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.




回答2:


The major difference between the two approaches is that

  1. The await version issues server requests sequentially in the loop. If one of them errors without being caught, no more requests are issued. If request errors are trapped using try/catch blocks, you can identify which request failed and perhaps code in some some form of recovery or even retry the operation.

  2. The Promise.all version will make server requests in or near parallel fashion, limited by browser restrictions on the maximum number of concurrent requests permitted. If one of the requests fails the Promise.all returned promise fails immediately. If any requests were successful and returned data, you lose the data returned. In addition if any request fails, no outstanding requests are cancelled - they were initiated in user code (the insert function) when creating the array of promises.

As mentioned in another answer, await is non blocking and returns to the event loop until its operand promise is settled. Both the Promise.all and await while looping versions allow responding to other events while requests are in progress.




回答3:


Each has different advantages, it's up to us which one we need to solve our problem.

await loop

for(let i = 0;i < SIZE; i++){
    await promiseCall();
}

It will call all promises in parallel if any promise rejected it won't have any effect on other promises.

In ES2018 it has simplified for certain situation like if you want to call the second iteration only if the first iteration got finished, refer the following ex.

async function printFiles () {
  const files = await getFilePaths()

  for await (const file of fs.readFile(file, 'utf8')) {
    console.log(contents)
  }
}

Promise.all()

var p1 = Promise.resolve(32);
var p2 = 123;
var p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 100);
});
Promise.all([p1, p2, p3]).then(values => { 
  console.log(values); // [32, 123, "foo"]
});

This will execute every promise sequentially and finally return combined revolved values array.

If any one of these promise get rejected it will return value of that rejected promise only. follow following ex,

var p1 = Promise.resolve(32);
var p2 = Promise.resolve(123);
var p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 100);
});
Promise.all([p1, p2, p3]).then(values => { 
  console.log(values); // 123
});


来源:https://stackoverflow.com/questions/53798589/await-loop-vs-promise-all

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!