你如何将变异数据带入循环中的回调?

时间:2017-03-06 02:33:08

标签: javascript node.js callback side-effects mutators

我经常遇到这种模式的问题,在循环内部有回调:

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;只是错误

这通常是当我编写一些复杂的解决方案以尝试保留或传递输入时,如果存在少量输入则有效,但是当您有数十亿输入时其实并非如此一个非常小的数字是成功的(提示,提示,组合锁实际上是一个很好的例子)。

这里是否有一个通用解决方案,将值传递给回调,就像回调函数首先评估它们一样?

2 个答案:

答案 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方法中的awaitdoAsyncThing关键字,并在运行时注意控制台。

希望这能澄清事情。

&#13;
&#13;
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;
&#13;
&#13;

相关问题