可以用let和var解释这种奇怪的行为吗?

时间:2015-12-03 11:32:18

标签: javascript node.js ecmascript-6 es6-promise

以下示例代码让我感到困惑......

"use strict";

var filesToLoad = [ 'fileA','fileB','fileC' ];
var promiseArray = [];

for( let i in filesToLoad ) {
  promiseArray.push(
    new Promise( function(resolve, reject ) {
      setTimeout( function() {
        resolve( filesToLoad[i] );
      }, Math.random() * 1000 );
    })
  );
}

Promise.all( promiseArray ).then(function(value) {
  console.log(value);
});

我感到困惑的原因是我期待控制台上有一个随机排序的输出。但我总是得到以下......

  

['fileA','fileB','fileC']

令我困惑的是,至少可以说,但是当我将让我更改为 var i 时,我真正感到困惑的是,我得到了以下结果。 ...

  

['fileC','fileC','fileC']

作为最近才试图完全理解Promise而不是很久以前开始使用let的人,我真的很难过。

进一步阅读...

在得到很多很棒的答案后,我重构了这个例子来摆脱循环和 i 。对大多数人来说似乎是显而易见的,但对我来说很有趣......

"use strict";

var filesToLoad = [ 'fileA','fileB','fileC' ];

function start( name )  {
    return new Promise( function(resolve, reject ) {
      setTimeout( function() {
        resolve( name + '_done' );
      }, Math.random() * 1000 );
    });
}

Promise.all( filesToLoad.map(start) ).then(function(value) {
  console.log(value);
});

5 个答案:

答案 0 :(得分:4)

这是因为关闭。阅读herehere

wd.findElement(By.xpath("//*[@id='shopByVehicle-search-change']/div[1]/div[1]")).click(); Select SelectMakedropdown = new Select(wd.findElement(By.id("vehicle-make"))); SelectMakedropdown.selectByVisibleText("BMW"); 也是块作用域,而let是函数作用域。

如果使用var

触发函数超时后,循环完成,我被设置为2.所以使用var i解决所有setTimeout函数。

如果使用filesToLoad[2]

由于它是块作用域,当函数被解析时,它会在声明let i时记住i的状态,所以当它被解析时它会使用正确的i值。

关于使用let i的输出顺序。

setTimeOut

给定Iterable(数组是Iterable),或Iterable的承诺,它产生promises(或promises和values的混合),迭代Iterable中的所有值到一个数组中并返回一个履行的promise当数组中的所有项都满足时。承诺的履行值是在原始阵列的各个位置处具有履行值的阵列。如果数组中的任何promise都拒绝,则返回的promise将被拒绝并拒绝原因。

因此,无论您的承诺得到解决的顺序如何,promise.all的结果将始终以正确的顺序解除承诺。

答案 1 :(得分:2)

为什么使用letvar会产生不同的结果:

使用let产生var所需结果的原因是,当使用let时,您声明了block-scoped variable,这样当循环移动到另一个迭代时当前循环内容的i值不受影响。

在for循环的标题中定义var变量并不意味着它仅在for循环的生命周期中存在,您将注意到如果执行以下操作:

for (var i = 0; i < 10; i++) { /*...*/ }

console.log(i); //=> 10

// `i` is already declared and its value will be 10
for (; i < 20; i++) { /*...*/ }

console.log(i); //=> 20

如果你使用Array#forEach,你可以完全避免这个问题,它通过在每次迭代中给你回调函数中的下一个值来为你完成filesToLoad[i]的工作:

filesToLoad.forEach((file) => {
  promiseArray.push(
    new Promise( function(resolve, reject ) {
      setTimeout( function() {
        resolve( file );
      }, Math.random() * 1000 );
    })
  );
});

______

使用letvar会影响Promise#all的行为吗?

没有。在您的示例中,promiseArray中Promise的位置定义了值添加到结果数组的顺序,而不是在解析每个Promise时。以随机间隔解析Promise的事实不会移动解析值在promiseArray内的位置。您所演示的是Promise#all生成一个值数组,其位置映射到产生其值的Promise。

有关Promise#all

的行为的详情,请参阅this answer
  

所有这些意味着只要输入严格排序(例如,数组),输出就会严格按输入排序。

答案 2 :(得分:1)

因为var的范围不是for的孩子,所以它是兄弟姐妹。所以循环运行一次并设置i = 0。然后再运行2次并设置i = 1然后i = 2。完成所有操作后,超时然后运行并运行resolve函数并传入resolve( filesToLoad[2] )let正常工作,因为以下循环迭代不会覆盖let i的值。

简而言之,超时仅在循环已经运行3次后运行,因此传递相同的值。我使用var创建了jsfiddle工作版本。

答案 3 :(得分:0)

这是letvar之间的差异。你的问题与Promises没有直接关系(除了它们运行异步的事实)。

<强> TL; DR

var声明被解释器移动到函数的开头,因此当执行超时时,循环已经在同一个变量上运行到数组的末尾。 let将变量保留在范围内,因此循环的每次迭代都使用不同的变量。

<强>背景

Javascript有一个名为 hoisting 的功能,这意味着变量或函数总是在它的包装函数的开头声明。如果编码器没有,则解释器移动它。因此:

var i;
for (i in array)
....

for (var i in array)
....

是等效的,即使有一些原因或其他编程背景,你会期望i在第二种情况下仅限于循环。

这就是为什么

alert(i);
...
var i=5;

警告undefined而不是抛出错误。就口译员而言 - 有一份声明。就像

var i;
alert(i);
...
i=5;

立即

ES6 / ES2015引入了let - 这是我们在上面第二个例子中的第一个假设。

alert(i);
...
let i=5;

实际上会抛出错误。到达alert时,没有i可以说。

在你的情况下

使用let,每次都使用一个新变量进行循环,因为let仅定义为循环的范围。每次迭代都是一个范围,每个都使用不同的变量(尽管在各自的范围内都命名为i

使用var时,您实际上在循环之前声明了i

var i;
for( i in filesToLoad ) {
  promiseArray.push(
    new Promise( function(resolve, reject ) {
      setTimeout( function() {
        resolve( filesToLoad[i] );
      }, Math.random() * 1000 );
    })
  );
}

所以它在每次迭代(或每个范围内)中都是相同的变量,这意味着在返回任何超时之前,循环将最后一项分配给i。这就是为什么结果是最后一项的3倍。

答案 4 :(得分:0)

这实际上是两个不同的,无关的问题。

您的第一个观察结果是输出是按原始顺序而不是随机顺序,这是由Promise.all的行为引起的。 Promise.all返回一个新的promise,当所有子promise都按原始顺序解析时,该promise将解析为数组。您可以将其视为Array.map,但可以将其视为承诺。

the Bluebird (a JS promise library) documentation中所述:

  

promise的履行值是一个数组,其在原始数组的各个位置具有履行值。

您的第二个问题是,将let更改为var如何将输出更改为具有所有相同的值,是由于数组作用域。简而言之,使用var定义的变量存在于for循环范围的。这意味着在每次迭代时,您实际上都在修改预先存在的变量的值,而不是创建新变量。由于Promise中的代码稍后执行,因此变量将具有上一次循环迭代的值。

let创建一个存在 in for次迭代的变量。这意味着,稍后在Promise中,您将访问在该迭代范围中定义的变量,该变量不会被下一个循环迭代覆盖。