使用辅助角色时如何停止承诺循环



let stop = false;
function functionA() {
return Promise.reject();
}
const processMe = function(label) {
if (stop) {
console.log("processMe stop");
return;
}
functionA()
.then(function() {
console.log(`${label} then handler`);
})
.catch(function(err) {
stop = true;
console.log(`${label} catch handler`);
});
};
console.log("Starting loop");
for (let n = 0; n < 10; ++n) {
if (stop) {
break;
}
setTimeout(function(){ 
processMe(n)
}, n * 1000);
}
console.log("Loop complete");

上面的代码不是100%完整的。如果它像现在一样运行,那么问题在于当函数 A 执行调用 Promise.reject() 的 Worker 时,运行此代码将导致:

Starting loop
Loop complete
0 catch handler
1 catch handler
2 catch handler
3 catch handler
4 catch handler
...

因此,使用"停止"标志将不起作用。

Chrome 上的行为与任何其他符合规范的 JavaScript 引擎的行为相同:如果processMefunctionA中的承诺在该循环中始终被拒绝,则每次添加到其中的catch个处理程序都将在循环完成后调用。promise 回调保证异步发生。 如果functionA本身引发异常,因为它是同步的(假设这不在async函数中),这将同步终止循环。但不是拒绝承诺。

例:

function functionA() {
return Promise.reject();
}
const processMe = function(label) {
functionA()
.then(function() {
console.log(`${label} then handler`);
})
.catch(function(err) {
console.log(`${label} catch handler`);
});
};
console.log("Starting loop");
for (let n = 0; n < 10; ++n) {
processMe(n);
}
console.log("Loop complete");

那里的输出是:

启动循环 循环完成 0 捕获处理程序 1 个抓捕处理程序 2 抓捕处理机

等等,在Chrome,Firefox,Edge和任何符合规范的引擎上。


您在评论中说过,您只希望触发一个catch。为此,请执行以下任一操作:

  • 如果您希望异步操作并行运行,请使用Promise.all,并且processMe返回承诺链(而不是使用catch)。然后在循环所在的位置使用catch
  • 如果您希望异步调用串联(一个接一个)而不是并行发生,请将承诺相互链接。

并行 — 请注意,我给了最后一个失败的 50% 几率,所以运行几次以查看成功和错误:

function functionA(n) {
console.log(`functionA starting ${n}`)
return new Promise((resolve, reject) => {
setTimeout(() => {
if (n === 9 && Math.random() < 0.5) {
reject(new Error(`error processing ${n}`));
} else {
resolve();
}
}, Math.random() * 1000);
});
}
const processMe = function(n) {
return functionA(n)
.then(result => {
console.log(`${n} then handler`);
return result;
});
};
console.log("Starting loop");
const promises = [];
for (let n = 0; n < 10; ++n) {
promises.push(processMe(n));
}
Promise.all(promises)
.then(() => {
console.log("All promises completed");
})
.catch(error => {
console.log(`Got rejection: ${error.message}`);
});
.as-console-wrapper {
max-height: 100% !important;
}

串联(1 个,共 2 个)(最后一个失败几率为 50%):

function functionA(n) {
console.log(`functionA starting ${n}`)
return new Promise((resolve, reject) => {
setTimeout(() => {
if (n === 9 && Math.random() < 0.5) {
reject(new Error(`error processing ${n}`));
} else {
resolve();
}
}, Math.random() * 200);
});
}
const processMe = function(n) {
return functionA(n)
.then(result => {
console.log(`${n} then handler`);
return result;
});
};
console.log("Starting loop");
let promise = Promise.resolve();
for (let n = 0; n < 10; ++n) {
promise = promise.then(() => processMe(n));
}
promise
.then(() => {
console.log("All promises completed");
})
.catch(error => {
console.log(`Got rejection: ${error.message}`);
});
.as-console-wrapper {
max-height: 100% !important;
}

串联(2 of 2):如果你的循环正在通过一个数组,并且你想串联它们,那么有一个成语,"promisereduce"成语。它看起来像这样(最后一个失败的几率为 50%)):

function functionA(n) {
console.log(`functionA starting ${n}`)
return new Promise((resolve, reject) => {
setTimeout(() => {
if (n === 9 && Math.random() < 0.5) {
reject(new Error(`error processing ${n}`));
} else {
resolve();
}
}, Math.random() * 200);
});
}
const processMe = function(n) {
return functionA(n)
.then(result => {
console.log(`${n} then handler`);
return result;
});
};
console.log("Starting loop");
const theArray = [0,1,2,3,4,5,6,7,8,9];
const promise = theArray.reduce((p, n) => {
return p.then(() => processMe(n));
}, Promise.resolve());
promise
.then(() => {
console.log("All promises completed");
})
.catch(error => {
console.log(`Got rejection: ${error.message}`);
});
.as-console-wrapper {
max-height: 100% !important;
}


¹ 承诺完成将排队进入承诺作业队列。由于 JavaScript 的运行到完成语义,在当前作业(运行循环)完成之前,无法运行 PromiseJobs 队列中的任何作业。

最新更新