我是javascript的新手,大部分都花时间编写后端java程序。在调用时,我无法弄清楚如何捕获范围变量的当前状态,例如: -
idList = [0, 1, 2, 3, 4, 5];
for(id in idList) {
new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() {
resolve(10);
}, 300);
}).then(function(num) {
console.log(id + ': ' + num);
return num * 2;
});
}
输出结果为: -
"5: 10"
"5: 10"
"5: 10"
"5: 10"
"5: 10"
"5: 10"
但我希望输出为
"0: 10"
"1: 10"
"2: 10"
"3: 10"
"4: 10"
"5: 10"
即希望然后捕获变量id的当前状态。
答案 0 :(得分:1)
有一些解决方案。
最容易实现(根据您提供的代码),只需在for循环中将let
放在id之前。
for (let id in idList) { ... }
由于您使用的for...in
循环是异步的,它在循环中包含的内部函数返回之前完成,因此id
将始终是循环结束时的任何内容(在此案例,5)。
必须使用let
而不是var
的原因是因为let
语句声明了一个块范围局部变量。
允许您将范围有限的变量声明为使用它的块,语句或表达式。这与var关键字不同,var关键字全局定义变量,或者无论块范围如何都在本地定义整个函数。(source)
因此,let
将起作用,var
(或全局变量,就像你拥有的那样)不会。
但是,建议在索引顺序很重要时不要在数组上使用for...in
循环。 (source)
更好的解决方案将使用for...of
循环,它将以一致的顺序访问元素。这也是一个相当简单的改变,用“of”交换单词“in”来创建循环。
for (let id of idList) { ... }
更具声明性的解决方案将使用在JavaScript中创建的每个数组上可用的forEach方法。这将为数组中的每个元素执行回调函数。
const idList = [0, 1, 2, 3, 4, 5];
idList.forEach(id => {
new Promise((resolve, reject) => {
setTimeout(() => resolve(10), 300);
})
.then((num) => {
console.log(id + ': ' + num);
return num * 2;
});
})
但请注意,forEach
将始终返回undefined,即使您为回调提供了返回值。在这种情况下,这似乎不是一个问题,因为你所要做的就是console.log
异步值进入。
答案 1 :(得分:0)
所以这里的问题是你使用for...in
并试图以这种方式迭代数组。这会带来id
的范围问题,当您的函数执行时,id
的值已经5
。
由于问题与范围界定有关,正如@wrleskovec在评论中提到的那样我们可以通过简单地改变来解决这个问题
for (id in idList)
到
for (let id in idList)
然而,we should avoid using for...in
for iterating over arrays。
我们可以通过在数组中使用forEach
封装id
来解决这个问题。
idList = [0, 1, 2, 3, 4, 5];
idList.forEach((id) => {
new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() {
resolve(10);
}, 300);
}).then(function(num) {
console.log(id + ': ' + num);
return num * 2;
});
});
另外作为附注,return num * 2;
目前没有做任何事情 - 不确定这是否是故意的。
答案 2 :(得分:0)
由于循环内部的方法是异步的,因此您必须使用:
Immediately-Invoked Function Expression捕获当前状态,以便您的代码看起来像这样。
for(var id in idList) {
(function(_id){
new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() {
resolve(10);
}, 300);
}).then(function(num) {
console.log(_id + ': ' + num);
return num * 2;
});
}(id);
}
一旦理解了IIFE,就可以开始使用一个轻松处理异步方法的模块,如async
使用此代码,您的代码将如下所示:
async.each(idList, function(id, callback) {
new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() {
resolve(10);
}, 300);
}).then(function(num) {
console.log(id + ': ' + num);
callback(null, num * 2);
return num * 2;
});
}, function(err, results) {
console.log('done with results %o', results);
});
我希望这有帮助!
答案 3 :(得分:0)
问题是你的for循环变量的范围。 for循环变量位于父函数或全局范围内。您可以在ES6中使用像let
这样的块范围变量,或者您需要通过将循环执行包装到函数中来创建新的变量范围。您应该阅读有关javascript范围和闭包如何工作的信息。
IMO最简单的解决方案是:
for(let id in idList) {
new Promise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() {
resolve(10);
}, 300);
}).then(function(num) {
console.log(id + ': ' + num);
return num * 2;
});
}
但是如果您在严格的ES6之前的环境中工作,则需要将循环执行上下文包装在函数中以保留范围内的循环迭代器。
答案 4 :(得分:0)
这是另一种方法
idList = [0, 1, 2, 3, 4, 5];
idList.reduce((p, id) => {
return p.then(function(id, value) {
console.log("%s : %s", id, value);
return new Promise(resolve =>
setTimeout(() => resolve(10), 300)
);
}.bind(null, id));
}, Promise.resolve(10));
答案 5 :(得分:0)
你可以试试这个
idList = [0, 1, 2, 3, 4, 5];
for(id in idList) {
new Promise(function(resolve, reject) {
// A mock async action using setTimeout
//save id value
var idValue = id;
setTimeout(function() {
resolve({idValue: idValue, num: 10});
}, 300);
}).then(function(obj) {
console.log(obj.idValue + ': ' + obj.num);
return obj.num * 2;
});
}