我能否按这些Promise完成的顺序来遍历它们,或获取一个按完成顺序排列的可迭代对象?
在我的代码里,我有一堆异步任务,它们将表示为 Promises。比如,可以设为:
let promises = myarr.map( (x) => myAsyncFn(x) );
我想在其中任意一个promise解决(settled)后触发某些操作;但我不想等到它们全部解决。
现在,我知道我可以用 Promise.race(promises) 从所有已解决的promise中取到第一个 Promise。但那只会得到一个Promise,且不返回其余promise的对象。我要的,是一个迭代器,或将promises转换成一个按解决顺序排列的 Promise 的可迭代对象,循坏它时意味着等待下一个settle,然后再下一个,以此类推。
解决方案
我猜简短的答案是否定的,但你可以这样做:
你可以在其中一个promise和一个已预先解析的“哨兵”值之间进行一次“竞速”(race)。如果竞速返回哨兵值,原始的Promise仍在待定状态。这就是在 ispending() 中发生的情况。现在,你可以用那种函数来测试整个promise数组,等待标记为ispending的 promise解析,然后你就会得到一个数组,指示哪些Promise仍在等待(然后我据此更新计量)。(与“pending”对应的替代状态要么是resolved要么是rejected——这在测试时要难一些)
下面是一个示例,展示如何获得在每个Promise上以及在所有Promise上调用的函数(我不确定你是否同时需要两者)。要在每个Promise上得到一个完成(done)的Promise,你在返回Promise的函数上追加 .then(onedone)。然后如果你需要在它们全部完成时触发某些东西,请使用 Promise.all(promises).then(alldone);。当全部完成时你将获得每个Promise的返回值的方式,是在 onedone() 回调函数中添加一个return语句。
let myarr = [0, 500, 1000, 1500, 2000];
let promises = myarr.map(time => myAsyncFn(time).then(onedone).then(status));
Promise.all(promises).then(alldone).then(status);
function myAsyncFn(time) {
return new Promise(resolve => {
setTimeout(resolve, time, `time: ${time}`);
});
}
function onedone(one) {
console.log(one);
return one; // return if you need all values in the alldone()
}
function alldone(all) {
console.log('all done: ', JSON.stringify(all));
}
async function ispending(promise) {
let sentinel = {};
let result = await Promise.race([promise, Promise.resolve(sentinel)]);
return result === sentinel;
}
async function status(item){
let pendingPromisesArr = promises.map(ispending);
Promise.all(pendingPromisesArr).then(all => {
let done = all.filter(pending => !pending);
document.getElementById('meter01').value = done.length / myarr.length;
});
return item;
}
<meter id="meter01" value="0" min="0" max="1"></meter>
备选方案1
Here's a function that maps input promises onto a new array of promises, each with its own captured resolver. As each input promise settles, it finds the first non-null resolver, calls it, and then nulls it.
要按照解决顺序循环新数组,逐个await。如果你想要生成器,把调用方的循环移到函数内部并逐个yield值。你可以通过让拒绝也解析为类似 { status, value / reason } 的值来处理拒绝情况,而不仅仅返回原值。
function inSettlementOrder(promises) {
const slots = [];
const resolvers = [];
for (let i = 0; i < promises.length; i++) {
slots[i] = new Promise(r => resolvers[i] = r);
}
/*
for (const p of promises) {
Promise.resolve(p).then(value => {
const i = resolvers.findIndex(r => r !== null);
resolvers[i](value);
resolvers[i] = null;
});
}
*/
// OP's idea to capture next index and avoid the find...
let next = 0;
for (const p of promises) {
Promise.resolve(p).then(value => {
resolvers[next](value);
resolvers[next] = null;
next++;
});
}
return slots;
}
async function test() {
const promises = [
new Promise(r => setTimeout(() => r("3 seconds"), 3000)),
new Promise(r => setTimeout(() => r("1 second"), 1000)),
new Promise(r => setTimeout(() => r("2 seconds"), 2000)),
];
const slots = inSettlementOrder(promises);
for (let i = 0; i < slots.length; i++) {
const value = await slots[i];
console.log(value);
}
}
test();
备选方案2
我理解你有一组“主要”任务,每个任务都有一个后续任务。你希望主要任务并行执行,但随后让后续任务按其关联的主要任务完成的顺序逐个执行。
跟随函数 sequence 通过维护一个主要任务数组和一个后续任务链来确保这一点。它在与主要任务数量相同的竞赛中执行尽可能多的race,且在每次竞赛之后
- 将获胜者替换为一个永远不会完成的Promise(这样第二快的主任务就会赢得第二次竞赛,依此类推)
- 使用
then将获胜者的后续任务追加到链的末端(这样这条链中的后续任务将按顺序执行)。这条链初始时是已解析的,因此一旦把第一个后续任务追加到链上,后续任务就会开始执行。
function timeoutPromise(s) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(s);
resolve(s);
}, s);
});
}
function indexedPromise(promise, index) {
return promise.then(function(res) {
return {result: res, index};
});
}
async function sequence(primary, followups) {
for (let i = 0; i < primary.length; i++)
primary[i] = indexedPromise(primary[i], i);
let followup = Promise.resolve();
for (let r = 0; r < primary.length; r++) {
const {result, index} = await Promise.race(primary);
primary[index] = new Promise(() => {});
followup = followup.then(() => Promise.resolve(result).then(followups[index]));
}
}
const primary = [];
const followups = [];
for (let i = 1; i <= 10; i++) {
primary[i - 1] = timeoutPromise(i * 100);
followups[i - 1] = s => timeoutPromise(s + i);
}
sequence(primary, followups);