等待多个并发 await 操作
Waiting for more than one concurrent await operation
如何更改以下代码以便触发两个异步操作并有机会同时 运行?
const value1 = await getValue1Async();
const value2 = await getValue2Async();
// use both values
我需要做这样的事情吗?
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
// use both values
我认为这应该可行:
const [value1, value2] = await Promise.all([getValue1Async(),getValue2Async()]);
下面是一个更详细的示例,以防有助于理解:
const promise1 = async() => {
return 3;
}
const promise2 = async() => {
return 42;
}
const promise3 = async() => {
return 500;
// emulate an error
// throw "something went wrong...";
}
const f1 = async() => {
try {
// returns an array of values
const results = await Promise.all([promise1(), promise2(), promise3()]);
console.log(results);
console.log(results[0]);
console.log(results[1]);
console.log(results[2]);
// assigns values to individual variables through 'array destructuring'
const [value1, value2, value3] = await Promise.all([promise1(), promise2(), promise3()]);
console.log(value1);
console.log(value2);
console.log(value3);
} catch (err) {
console.log("there was an error: " + err);
}
}
f1();
TL;DR
不要在获取promises的问题中使用模式,然后单独等待它们;相反,使用 Promise.all
(至少现在):
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
虽然您的解决方案 运行 这两个操作是并行的,但如果两个承诺都拒绝,它就无法正确处理拒绝。
详情:
您的解决方案 运行 并行处理它们,但总是等待第一个完成,然后再等待第二个。 如果你只是想启动它们,运行它们并行,并得到两个结果,那很好。 (不,不是,保持正在阅读...) 请注意,如果第一个需要(比方说)五秒钟完成,而第二个在一秒钟内失败,您的代码将等待整整五秒钟然后失败。
可悲的是,目前没有 await
语法来进行并行等待,所以你有你列出的尴尬,或者 Promise.all
。 (虽然有 been discussion of await.all
or similar,也许有一天。)
Promise.all
版本是:
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
...更简洁,如果第二个操作很快失败,也不会等待第一个操作完成(例如,在我上面的五秒/一秒示例中,上面将在一秒钟内拒绝而不是等待五个)。另请注意,对于您的原始代码,如果第二个承诺在第一个承诺解决之前被拒绝,您可能会在控制台中收到 "unhandled rejection" 错误(您目前使用 Chrome v61; 更新: 较新的版本有 ), although that error is arguably spurious (because you do, eventually, handle the rejection, in that this code is clearly in an async
function¹ and so that function will hook rejection and make its promise reject with it) (update: again, )。但是如果 both 承诺拒绝,你会得到一个真正的未处理的拒绝错误,因为控制流永远不会达到 const value2 = await p2;
,因此 p2 拒绝永远不会被处理。
未处理的拒绝是一件坏事™(如此之多以至于很快,Node.js 将中止真正未处理的拒绝的过程,就像未处理的异常一样——因为它们就是这样),所以最好避免"get the promise then await
it" 你问题中的模式。
这是失败情况下时间差异的示例(使用 500 毫秒和 100 毫秒而不是 5 秒和 1 秒),也可能是有争议的虚假未处理拒绝错误(打开 real 浏览器控制台看看吧):
const getValue1Async = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, "value1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error");
});
};
// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
try {
console.time("separate");
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
} catch (e) {
console.error(e);
}
console.timeEnd("separate");
})();
// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
try {
console.time("Promise.all");
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
} catch (e) {
console.timeEnd("Promise.all", e);
}
}, 1000);
Open the real browser console to see the unhandled rejection error.
这里我们拒绝 p1
和 p2
,导致 p2
:
出现非虚假的未处理拒绝错误
const getValue1Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 500, "error1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error2");
});
};
// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
try {
console.time("separate");
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
} catch (e) {
console.error(e);
}
console.timeEnd("separate");
})();
// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
try {
console.time("Promise.all");
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
} catch (e) {
console.timeEnd("Promise.all", e);
}
}, 1000);
Open the real browser console to see the unhandled rejection error.
在您提出的评论中:
Side question: will the following force waiting for both (and discarding the results) await p1 && await p2
?
这与您的原始代码在承诺拒绝方面存在相同的问题:它会等到 p1
解决,即使 p2
更早拒绝;如果 p2
在 p1
解决之前拒绝,它可能会产生一个有争议的虚假 (update: ) 未处理的拒绝错误;如果 p1
和 p2
都拒绝(因为从未处理 p2
的拒绝),它会生成一个真正的未处理拒绝错误。
这里是 p1
解决而 p2
拒绝的情况:
const getValue1Async = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, false);
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error");
});
};
(async () => {
try {
const p1 = getValue1Async();
const p2 = getValue2Async();
console.log("waiting");
await p1 && await p2;
} catch (e) {
console.error(e);
}
console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).
...并且两者都拒绝:
const getValue1Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 500, "error1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error2");
});
};
(async () => {
try {
const p1 = getValue1Async();
const p2 = getValue2Async();
console.log("waiting");
await p1 && await p2;
} catch (e) {
console.error(e);
}
console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).
¹ "...此代码显然在 async
函数中..." 在 2017 年编写此问答时确实如此。从此,top-level await
happened/is发生了。
使用 .catch() 和 Promise.all()
确保您正确处理拒绝,并且您可以安全地使用 Promises.all() 而不会遇到未处理的拒绝。 (编辑:每个讨论的澄清:不是错误 unhandled rejection
,而是代码未处理的简单拒绝。Promise.all()
将抛出第一个承诺拒绝,并将 忽略 其余)。
在下面的示例中,返回了一个 [[error, results], ...] 数组,以便于处理结果 and/or 错误。
let myTimeout = (ms, is_ok) =>
new Promise((resolve, reject) =>
setTimeout(_=> is_ok ?
resolve(`ok in ${ms}`) :
reject(`error in ${ms}`),
ms));
let handleRejection = promise => promise
.then((...r) => [null, ...r])
.catch(e => [e]);
(async _=> {
let res = await Promise.all([
myTimeout(100, true),
myTimeout(200, false),
myTimeout(300, true),
myTimeout(400, false)
].map(handleRejection));
console.log(res);
})();
您可以从 catch() 中抛出以停止等待所有(并丢弃其余的结果),但是 - 您只能在每个 try/catch 块中执行一次,因此标志 has_thorwn 需要维护和检查以确保没有未处理的错误发生。
let myTimeout = (ms, is_ok) =>
new Promise((resolve, reject) =>
setTimeout(_=> is_ok ?
resolve(`ok in ${ms}`) :
reject(`error in ${ms}`),
ms));
let has_thrown = false;
let handleRejection = promise => promise
.then((...r) => [null, ...r])
.catch(e => {
if (has_thrown) {
console.log('not throwing', e);
} else {
has_thrown = 1;
throw e;
}
});
(async _=> {
try {
let res = await Promise.all([
myTimeout(100, true),
myTimeout(200, false),
myTimeout(300, true),
myTimeout(400, false)
].map(handleRejection));
console.log(res);
} catch(e) {
console.log(e);
}
console.log('we are done');
})();
解决而不是承诺
const wait = (ms, data) => new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err:' + e
const l = l => (console.log(l), l)
;(async function parallel() {
let task1 = reject(500, 'parallelTask1').catch(e).then(l)
let task2 = wait(2500, 'parallelTask2').catch(e).then(l)
let task3 = reject(1500, 'parallelTask3').catch(e).then(l)
console.log('WAITING')
;[task1, task2, task3] = [await task1, await task2, await task3]
console.log('FINISHED', task1, task2, task3)
})()
正如其他答案中所指出的,被拒绝的承诺可能会引发未处理的异常。
这个 .catch(e => e)
是一个巧妙的小技巧,可以捕获错误并将其传递到链中,从而允许 resolve
而不是 rejecting
.
如果你觉得这个 ES6 代码丑陋,请看更友好的 here。
如何更改以下代码以便触发两个异步操作并有机会同时 运行?
const value1 = await getValue1Async();
const value2 = await getValue2Async();
// use both values
我需要做这样的事情吗?
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
// use both values
我认为这应该可行:
const [value1, value2] = await Promise.all([getValue1Async(),getValue2Async()]);
下面是一个更详细的示例,以防有助于理解:
const promise1 = async() => {
return 3;
}
const promise2 = async() => {
return 42;
}
const promise3 = async() => {
return 500;
// emulate an error
// throw "something went wrong...";
}
const f1 = async() => {
try {
// returns an array of values
const results = await Promise.all([promise1(), promise2(), promise3()]);
console.log(results);
console.log(results[0]);
console.log(results[1]);
console.log(results[2]);
// assigns values to individual variables through 'array destructuring'
const [value1, value2, value3] = await Promise.all([promise1(), promise2(), promise3()]);
console.log(value1);
console.log(value2);
console.log(value3);
} catch (err) {
console.log("there was an error: " + err);
}
}
f1();
TL;DR
不要在获取promises的问题中使用模式,然后单独等待它们;相反,使用 Promise.all
(至少现在):
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
虽然您的解决方案 运行 这两个操作是并行的,但如果两个承诺都拒绝,它就无法正确处理拒绝。
详情:
您的解决方案 运行 并行处理它们,但总是等待第一个完成,然后再等待第二个。 如果你只是想启动它们,运行它们并行,并得到两个结果,那很好。 (不,不是,保持正在阅读...) 请注意,如果第一个需要(比方说)五秒钟完成,而第二个在一秒钟内失败,您的代码将等待整整五秒钟然后失败。
可悲的是,目前没有 await
语法来进行并行等待,所以你有你列出的尴尬,或者 Promise.all
。 (虽然有 been discussion of await.all
or similar,也许有一天。)
Promise.all
版本是:
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
...更简洁,如果第二个操作很快失败,也不会等待第一个操作完成(例如,在我上面的五秒/一秒示例中,上面将在一秒钟内拒绝而不是等待五个)。另请注意,对于您的原始代码,如果第二个承诺在第一个承诺解决之前被拒绝,您可能会在控制台中收到 "unhandled rejection" 错误(您目前使用 Chrome v61; 更新: 较新的版本有 async
function¹ and so that function will hook rejection and make its promise reject with it) (update: again, const value2 = await p2;
,因此 p2 拒绝永远不会被处理。
未处理的拒绝是一件坏事™(如此之多以至于很快,Node.js 将中止真正未处理的拒绝的过程,就像未处理的异常一样——因为它们就是这样),所以最好避免"get the promise then await
it" 你问题中的模式。
这是失败情况下时间差异的示例(使用 500 毫秒和 100 毫秒而不是 5 秒和 1 秒),也可能是有争议的虚假未处理拒绝错误(打开 real 浏览器控制台看看吧):
const getValue1Async = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, "value1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error");
});
};
// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
try {
console.time("separate");
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
} catch (e) {
console.error(e);
}
console.timeEnd("separate");
})();
// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
try {
console.time("Promise.all");
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
} catch (e) {
console.timeEnd("Promise.all", e);
}
}, 1000);
Open the real browser console to see the unhandled rejection error.
这里我们拒绝 p1
和 p2
,导致 p2
:
const getValue1Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 500, "error1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error2");
});
};
// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
try {
console.time("separate");
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
} catch (e) {
console.error(e);
}
console.timeEnd("separate");
})();
// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
try {
console.time("Promise.all");
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
} catch (e) {
console.timeEnd("Promise.all", e);
}
}, 1000);
Open the real browser console to see the unhandled rejection error.
在您提出的评论中:
Side question: will the following force waiting for both (and discarding the results)
await p1 && await p2
?
这与您的原始代码在承诺拒绝方面存在相同的问题:它会等到 p1
解决,即使 p2
更早拒绝;如果 p2
在 p1
解决之前拒绝,它可能会产生一个有争议的虚假 (update: p1
和 p2
都拒绝(因为从未处理 p2
的拒绝),它会生成一个真正的未处理拒绝错误。
这里是 p1
解决而 p2
拒绝的情况:
const getValue1Async = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, false);
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error");
});
};
(async () => {
try {
const p1 = getValue1Async();
const p2 = getValue2Async();
console.log("waiting");
await p1 && await p2;
} catch (e) {
console.error(e);
}
console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).
...并且两者都拒绝:
const getValue1Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 500, "error1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error2");
});
};
(async () => {
try {
const p1 = getValue1Async();
const p2 = getValue2Async();
console.log("waiting");
await p1 && await p2;
} catch (e) {
console.error(e);
}
console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).
¹ "...此代码显然在 async
函数中..." 在 2017 年编写此问答时确实如此。从此,top-level await
happened/is发生了。
使用 .catch() 和 Promise.all()
确保您正确处理拒绝,并且您可以安全地使用 Promises.all() 而不会遇到未处理的拒绝。 (编辑:每个讨论的澄清:不是错误 unhandled rejection
,而是代码未处理的简单拒绝。Promise.all()
将抛出第一个承诺拒绝,并将 忽略 其余)。
在下面的示例中,返回了一个 [[error, results], ...] 数组,以便于处理结果 and/or 错误。
let myTimeout = (ms, is_ok) =>
new Promise((resolve, reject) =>
setTimeout(_=> is_ok ?
resolve(`ok in ${ms}`) :
reject(`error in ${ms}`),
ms));
let handleRejection = promise => promise
.then((...r) => [null, ...r])
.catch(e => [e]);
(async _=> {
let res = await Promise.all([
myTimeout(100, true),
myTimeout(200, false),
myTimeout(300, true),
myTimeout(400, false)
].map(handleRejection));
console.log(res);
})();
您可以从 catch() 中抛出以停止等待所有(并丢弃其余的结果),但是 - 您只能在每个 try/catch 块中执行一次,因此标志 has_thorwn 需要维护和检查以确保没有未处理的错误发生。
let myTimeout = (ms, is_ok) =>
new Promise((resolve, reject) =>
setTimeout(_=> is_ok ?
resolve(`ok in ${ms}`) :
reject(`error in ${ms}`),
ms));
let has_thrown = false;
let handleRejection = promise => promise
.then((...r) => [null, ...r])
.catch(e => {
if (has_thrown) {
console.log('not throwing', e);
} else {
has_thrown = 1;
throw e;
}
});
(async _=> {
try {
let res = await Promise.all([
myTimeout(100, true),
myTimeout(200, false),
myTimeout(300, true),
myTimeout(400, false)
].map(handleRejection));
console.log(res);
} catch(e) {
console.log(e);
}
console.log('we are done');
})();
解决而不是承诺
const wait = (ms, data) => new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err:' + e
const l = l => (console.log(l), l)
;(async function parallel() {
let task1 = reject(500, 'parallelTask1').catch(e).then(l)
let task2 = wait(2500, 'parallelTask2').catch(e).then(l)
let task3 = reject(1500, 'parallelTask3').catch(e).then(l)
console.log('WAITING')
;[task1, task2, task3] = [await task1, await task2, await task3]
console.log('FINISHED', task1, task2, task3)
})()
正如其他答案中所指出的,被拒绝的承诺可能会引发未处理的异常。
这个 .catch(e => e)
是一个巧妙的小技巧,可以捕获错误并将其传递到链中,从而允许 resolve
而不是 rejecting
.
如果你觉得这个 ES6 代码丑陋,请看更友好的 here。