使用外部变量和Javascript然后

时间:2016-12-01 02:58:59

标签: javascript node.js promise

我是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的当前状态。

6 个答案:

答案 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)

由于循环内部的方法是异步的,因此您必须使用:

IIFE

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; 
    });
}