在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个请求花费的时间更长,则直到返回所有响应后才会执行回调。
答案 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秒。
也许图片会有所帮助:
注意:Promise.all
实际上不会完全并行地运行请求,并且总体而言,性能将很大程度上取决于代码在其中运行的确切环境及其状态(例如,事件循环),但这是一个很好的近似值。
答案 1 :(得分:3)
两种方法的主要区别在于
await
版本在循环中顺序发出服务器请求。如果其中一个错误没有被捕获,则不会发出更多请求。如果使用try / catch块捕获了请求错误,则可以确定哪个请求失败,并可能以某种形式的恢复进行编码,甚至重试该操作。
Promise.all
版本将以并行方式或接近并行方式发出服务器请求,但受浏览器对允许的maximum number of concurrent requests的限制所限制。如果其中一个请求失败,则返回的Promise.all
承诺将立即失败。如果有任何请求成功并返回了数据,则您将丢失返回的数据。另外,如果有任何请求失败,则未取消的请求不会被取消-在创建承诺数组时,它们是通过用户代码(insert
函数)启动的。
如另一个答案中所述,await
是非阻塞的,并返回到事件循环,直到其操作数承诺被确定为止。循环版本的Promise.all
和await
都可以在请求进行过程中响应其他事件。
答案 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
});