我经常遇到这种模式的问题,在循环内部有回调:
while(input.notEnd()) {
input.next();
checkInput(input, (success) => {
if (success) {
console.log(`Input ${input.combo} works!`);
}
});
}
这里的目标是检查input
的每个可能值,并显示确认后通过异步测试的值。假设checkInput
函数执行此测试,返回布尔通过/失败,并且是外部库的一部分,无法修改。
让我们说input
循环通过多码电子珠宝安全的所有组合,.next
增加组合,.combo
读出当前组合,{ {1}}异步检查组合是否正确。正确的组合是05-30-96,18-22-09,59-62-53,68-82-01是85-55-85。您期望看到的输出是这样的:
checkInput
相反,因为在调用回调时,Input 05-30-96 works!
Input 18-22-09 works!
Input 59-62-53 works!
Input 68-82-01 works!
Input 85-55-85 works!
已经提前了不确定的次数,并且循环可能已经终止,您可能会看到如下内容:< / p>
input
如果循环已经终止,至少显然会出现问题。如果Input 99-99-99 works!
Input 99-99-99 works!
Input 99-99-99 works!
Input 99-99-99 works!
Input 99-99-99 works!
函数速度特别快,或者循环速度特别慢,那么您可能会获得 随机输出 ,具体取决于checkInput
恰好位于回调检查它的那一刻。
如果您发现输出完全是随机的,这是一个非常难以追踪的错误,而且对我的提示往往是您总是得到预期的数字输出,他们&#39;只是错误。
这通常是当我编写一些复杂的解决方案以尝试保留或传递输入时,如果存在少量输入则有效,但是当您有数十亿输入时其实并非如此一个非常小的数字是成功的(提示,提示,组合锁实际上是一个很好的例子)。
这里是否有一个通用解决方案,将值传递给回调,就像回调函数首先评估它们一样?
答案 0 :(得分:2)
如果要一次迭代一个异步操作,则不能使用while
循环。 Javascript中的异步操作不会阻塞。那么,你的while
循环在每个值上调用checkInput()
的整个循环运行,然后在将来某个时候调用每个回调。他们甚至可能无法以所需的顺序被召唤。
因此,根据您希望它的工作方式,您有两种选择。
首先,你可以使用一种不同类型的循环,它只在异步操作完成时才进入循环的下一次迭代。
或者,第二,你可以像平行一样以并行方式运行它们,并为每个回调唯一地捕获对象的状态。
我假设您可能想要做的是对异步操作进行排序(第一个选项)。
排序异步操作
以下是如何做到这一点(适用于ES5或ES6):
function next() {
if (input.notEnd()) {
input.next();
checkInput(input, success => {
if (success) {
// because we are still on the current iteration of the loop
// the value of input is still valid
console.log(`Input ${input.combo} works!`);
}
// do next iteration
next();
});
}
}
next();
并行运行,在ES6的本地范围内保存相关属性
如果您希望像原始代码一样并行运行它们,但仍然能够在回调中引用正确的input.combo
属性,那么您必须将该属性保存在闭包(上面的第二个选项)let
使得while
变得相当简单,因为它对于let
循环的每次迭代都是单独的块范围,因此在回调运行时保留其值并且不会被其他迭代重写循环(需要ES6支持while(input.notEnd()) {
input.next();
// let makes a block scoped variable that will be separate for each
// iteration of the loop
let combo = input.combo;
checkInput(input, (success) => {
if (success) {
console.log(`Input ${combo} works!`);
}
});
}
):
let
并行运行,在ES5的本地范围内保存相关属性
在ES5中,您可以引入一个函数作用域来解决while(input.notEnd()) {
input.next();
// create function scope to save value separately for each
// iteration of the loop
(function() {
var combo = input.combo;
checkInput(input, (success) => {
if (success) {
console.log(`Input ${combo} works!`);
}
});
})();
}
在ES6中所做的相同问题(为循环的每次迭代创建一个新作用域):
span
答案 1 :(得分:0)
您可以将新功能async await
用于异步调用,这样可以让您在循环内等待checkInput
方法完成。
您可以阅读有关async await here
的更多信息我相信下面的代码段实现了你所追求的目标,我创建了一个MockInput函数,它应该模拟输入的行为。请注意Async
方法中的await
和doAsyncThing
关键字,并在运行时注意控制台。
希望这能澄清事情。
function MockInput() {
this.currentIndex = 0;
this.list = ["05-30-96", "18-22-09", "59-62-53", "68-82-0", "85-55-85"];
this.notEnd = function(){
return this.currentIndex <= 4;
};
this.next = function(){
this.currentIndex++;
};
this.combo = function(){
return this.list[this.currentIndex];
}
}
function checkInput(input){
return new Promise(resolve => {
setTimeout(()=> {
var isValid = input.currentIndex % 2 > 0; // 'random' true or false
resolve( `Input ${input.currentIndex} - ${input.combo()} ${isValid ? 'works!' : 'did not work'}`);
}, 1000);
});
}
async function doAsyncThing(){
var input = new MockInput();
while(input.notEnd()) {
var result = await checkInput(input);
console.log(result);
input.next();
}
console.log('Done!');
}
doAsyncThing();
&#13;