等待循环vs Promise.all

时间:2018-12-16 01:06:01

标签: javascript node.js ecmascript-6 es6-promise

在db上有一组异步操作要做,我想知道执行“阻塞” await循环与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解决方案(它需要一个for循环来构建promise数组。)

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

等待循环解决方案

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

编辑:谢谢anwsers,但我将对此进行深入探讨。 await并不是真正的阻塞,我们都知道,它在自己的代码块中处于阻塞状态。 await循环顺序触发请求,因此,如果在中间的1个请求花费更长的时间,则其他请求会等待。 好吧,这类似于Promise.all:如果1个请求花费的时间更长,则直到返回所有响应后才会执行回调。

3 个答案:

答案 0 :(得分:4)

您使用(21 ∪ 10)*0012*的示例将首先创建所有承诺,然后再等待它们解决。这意味着您的请求将同时触发,并且Promise.all的回调仅在所有请求都成功的情况下触发。

注意:从Promise.all(...).then(thisCallback)返回的promise将在给定数组中的promise之一拒绝后立即拒绝。

Promise.all

注意:在这种情况下,使用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而不是循环可能更干净:

.map

另一方面,您使用Promise.all( Array.from(Array(SIZE)).map((_, i) => insert(i,"..string..")) ).then(values => { console.log("promise all ends"); }); 的示例将等待每个诺言得到解决,然后继续执行下一个诺言:

await

以上情况对绩效的影响与他们不同的行为直接相关。

如果对您的用例而言“高效”意味着尽快完成请求,则第一个示例将获胜,因为请求将在大约同一时间独立发生,而在第二个示例中,它们将在同一时间发生。连续时尚。

就复杂度而言,第一个示例的时间复杂度等于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,因为请求本质上是并行发生的,因此花费时间最长的请求将导致最坏的情况。

另一方面,O(longestRequestTime)示例具有await,因为无论各个请求花费多长时间,每个请求都必须等待上一个请求完成,因此总时间将始终包括所有请求。其中

将所有事物都考虑在内,忽略由于代码运行环境和应用程序而导致的所有其他潜在延迟,对于1000个请求,每个请求花费1s,O(sumOfAllRequestTimes)示例仍然花费〜1s,而{ {1}}示例大约需要1000秒。

也许图片会有所帮助:

time comparison chart

注意:Promise.all实际上不会完全并行地运行请求,并且总体而言,性能将很大程度上取决于代码在其中运行的确切环境及其状态(例如,事件循环),但这是一个很好的近似值。

答案 1 :(得分:3)

两种方法的主要区别在于

  1. await版本在循环中顺序发出服务器请求。如果其中一个错误没有被捕获,则不会发出更多请求。如果使用try / catch块捕获了请求错误,则可以确定哪个请求失败,并可能以某种形式的恢复进行编码,甚至重试该操作。

  2. Promise.all版本将以并行方式或接近并行方式发出服务器请求,但受浏览器对允许的maximum number of concurrent requests的限制所限制。如果其中一个请求失败,则返回的Promise.all承诺将立即失败。如果有任何请求成功并返回了数据,则您将丢失返回的数据。另外,如果有任何请求失败,则未取消的请求不会被取消-在创建承诺数组时,它们是通过用户代码(insert函数)启动的。

如另一个答案中所述,await是非阻塞的,并返回到事件循环,直到其操作数承诺被确定为止。循环版本的Promise.allawait都可以在请求进行过程中响应其他事件。

答案 2 :(得分:1)

每个都有不同的优势,这取决于我们要解决我们的问题。

  

等待循环

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

如果任何承诺被拒绝,它将并行调用所有承诺,这不会对其他承诺产生任何影响。

在ES2018中,它已针对某些情况进行了简化,例如,如果仅在第一次迭代完成后才想调用第二次迭代,请参考以下示例。

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"]
});

这将顺序执行每个promise,最后返回组合的旋转值数组。

如果这些承诺中的任何一个被拒绝,它将仅返回该被拒绝承诺的值。遵循以下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
});