您如何将变异数据携带到循环内的回调中?
How do you carry mutating data into callbacks within loops?
我经常 运行 遇到这种模式在循环内回调的问题:
while(input.notEnd()) {
input.next();
checkInput(input, (success) => {
if (success) {
console.log(`Input ${input.combo} works!`);
}
});
}
这里的目标是检查input
的每一个可能的值,并在确认后显示通过异步测试的那些。假设 checkInput
函数执行此测试,返回一个布尔值 pass/fail,并且是外部库的一部分并且无法修改。
假设 input
循环遍历多代码电子珠宝保险箱的所有组合,.next
递增组合,.combo
读出当前组合,checkInput
异步检查组合是否正确。正确的组合是 05-30-96、18-22-09、59-62-53、68-82-01 和 85-55-85。您希望看到的输出是这样的:
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!
相反,因为在回调被调用时,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
函数特别快,或者循环特别慢,您可能会得到 随机输出 取决于 input
恰好在回调检查它的那一刻。
如果你发现你的输出是完全随机的,这是一个非常难以追踪的错误,对我来说,提示往往是你总是得到预期的 number 输出, 他们只是 错误 .
这通常是在我编写一些复杂的解决方案来尝试保留或传递输入时,如果输入数量很少,这会起作用,但当你有数十亿个输入时,它就不会起作用,其中极少数是成功的(提示,提示,组合锁实际上是一个很好的例子)。
这里是否有一个通用的解决方案,将值传递给回调,就像带有回调的函数第一次计算它们时的值一样?
如果您想一次迭代一个异步操作,则不能使用 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
属性,那么您d 必须将 属性 保存在一个闭包中(上面的第二个选项),这 let
变得相当容易,因为它对于 while
循环的每次迭代都是单独的块范围,因此保留其值当回调 运行s 并且没有被循环的其他迭代覆盖时(需要 ES6 支持 let
):
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!`);
}
});
}
运行并行,在ES5
的本地范围内保存相关属性
在 ES5 中,您可以引入函数作用域来解决 let
在 ES6 中所做的相同问题(为循环的每次迭代创建一个新作用域):
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!`);
}
});
})();
}
您可以使用新功能 async await
进行异步调用,这样您就可以在循环内等待 checkInput
方法完成。
您可以阅读有关异步等待的更多信息here
我相信下面的代码片段可以实现您的目标,我创建了一个模拟输入行为的 MockInput 函数。注意 doAsyncThing
方法中的 Async
和 await
关键字,并在 运行 时留意控制台。
希望这能澄清事情。
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();
我经常 运行 遇到这种模式在循环内回调的问题:
while(input.notEnd()) {
input.next();
checkInput(input, (success) => {
if (success) {
console.log(`Input ${input.combo} works!`);
}
});
}
这里的目标是检查input
的每一个可能的值,并在确认后显示通过异步测试的那些。假设 checkInput
函数执行此测试,返回一个布尔值 pass/fail,并且是外部库的一部分并且无法修改。
假设 input
循环遍历多代码电子珠宝保险箱的所有组合,.next
递增组合,.combo
读出当前组合,checkInput
异步检查组合是否正确。正确的组合是 05-30-96、18-22-09、59-62-53、68-82-01 和 85-55-85。您希望看到的输出是这样的:
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!
相反,因为在回调被调用时,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
函数特别快,或者循环特别慢,您可能会得到 随机输出 取决于 input
恰好在回调检查它的那一刻。
如果你发现你的输出是完全随机的,这是一个非常难以追踪的错误,对我来说,提示往往是你总是得到预期的 number 输出, 他们只是 错误 .
这通常是在我编写一些复杂的解决方案来尝试保留或传递输入时,如果输入数量很少,这会起作用,但当你有数十亿个输入时,它就不会起作用,其中极少数是成功的(提示,提示,组合锁实际上是一个很好的例子)。
这里是否有一个通用的解决方案,将值传递给回调,就像带有回调的函数第一次计算它们时的值一样?
如果您想一次迭代一个异步操作,则不能使用 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
属性,那么您d 必须将 属性 保存在一个闭包中(上面的第二个选项),这 let
变得相当容易,因为它对于 while
循环的每次迭代都是单独的块范围,因此保留其值当回调 运行s 并且没有被循环的其他迭代覆盖时(需要 ES6 支持 let
):
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!`);
}
});
}
运行并行,在ES5
的本地范围内保存相关属性在 ES5 中,您可以引入函数作用域来解决 let
在 ES6 中所做的相同问题(为循环的每次迭代创建一个新作用域):
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!`);
}
});
})();
}
您可以使用新功能 async await
进行异步调用,这样您就可以在循环内等待 checkInput
方法完成。
您可以阅读有关异步等待的更多信息here
我相信下面的代码片段可以实现您的目标,我创建了一个模拟输入行为的 MockInput 函数。注意 doAsyncThing
方法中的 Async
和 await
关键字,并在 运行 时留意控制台。
希望这能澄清事情。
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();