简体   繁体   English

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

[英]Can this strange behavior with let and var be explained?

The following example code confuses me... 以下示例代码让我困惑......

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

The reason I'm confused is that I was expecting a random ordered output on the console. 我很困惑的原因是我期待控制台上有一个随机排序的输出。 But I always get the following... 但我总是得到以下......

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

That confuses me little to say the least, but what really gets me scratching my head is when I change the let i to var i I get the following result.... 这至少让我感到困惑,但真正让我摸不着头脑的是当我改变let i to var i时,我得到以下结果....

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

As someone who has only recently tried to fully understand Promises and not that long ago starting using let, I'm really stumped. 作为一个最近才试图完全理解Promise而不是很久以前开始使用let的人,我真的很难过。

Further reading... 进一步阅读......

After getting lots of great answers I have refactored the example to get rid of the loop and i . 在得到很多很棒的答案后,我重构了这个例子来摆脱循环和 Will seem obvious to most, but fun for me... 对大多数人来说似乎很明显,但对我来说很有趣......

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

It is because of closure. 这是因为关闭。 Read about it here and here . herehere阅读它。

Also let is block scoped whereas var is function scoped. 另外, let是块作用域,而var是函数作用域。

In case of using var i : 如果使用var i

After timeout when the function is triggered, looping was completed and i was set to 2. so it got resolved with filesToLoad[2] for all the setTimeout functions. 在触发函数超时后,循环完成并且我被设置为2.所以它通过filesToLoad[2]解决了所有setTimeout函数。

In case of using let i : 如果使用let i

Since it is block scoped, when function is resolved it remembers the state of i when setTimeOut was declared, so when it gets resolved it uses correct value of i. 由于它是块作用域,当函数被解析时,它会在声明setTimeOut时记住i的状态,因此当它被解析时它使用正确的i值。

Regarding the order of output in case of using let i. 关于使用let i的输出顺序。

Promise.all(Iterable<any>|Promise<Iterable<any>> input) -> Promise

Given an Iterable(arrays are Iterable), or a promise of an Iterable, which produces promises (or a mix of promises and values), iterate over all the values in the Iterable into an array and return a promise that is fulfilled when all the items in the array are fulfilled. 给定Iterable(数组是Iterable),或Iterable的承诺,它产生promises(或promises和values的混合),迭代Iterable中的所有值到一个数组中并返回一个在所有的时候满足的promise数组中的项目已完成。 The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. promise的履行值是一个数组,其在原始数组的各个位置具有履行值。 If any promise in the array rejects, the returned promise is rejected with the rejection reason. 如果数组中的任何promise都拒绝,则返回的promise将被拒绝并拒绝原因。

So irrespective of order in which your promises gets resolved, the result of promise.all will always have promise resolve value in correct order. 因此,无论您的承诺得到解决的顺序如何,promise.all的结果将始终具有正确顺序的承诺解析价值。

Why using let and var produces different results: 为什么使用letvar产生不同的结果:

The reason that using let produces the desired result over var is that when using let you declare a block-scoped variable , such that when the loop moves to another iteration the value of i remains unaffected for the contents of the loop at that time. 在使用的原因let生产超过所需的结果var是,当使用let你声明一个块范围的变量 ,使得当所述环移动到另一个迭代的值i不受影响对于此时的循环的内容。

Defining a var variable in the header of a for-loop does not mean that it exists only for the life of the for-loop, as you will notice if you do the following: 在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

You can avoid this problem altogether if you use Array#forEach , which does the job of filesToLoad[i] for you by giving you the next value within a callback function on each iteration: 如果使用Array#forEach ,它可以完全避免这个问题,它通过在每次迭代中为回调函数提供下一个值来为您完成filesToLoad[i]的工作:

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

______ ______

Does using either let or var affect the behaviour of Promise#all ? 使用letvar会影响Promise#all的行为吗?

No. In your example, the position of the Promises in promiseArray defines in what order the values are added to the results array, not when each of those Promises is resolved. 在您的示例中, promiseArraypromiseArray的位置定义了值添加到结果数组的顺序,而不是在解析每个promiseArray时。 The fact that you resolve the Promises at random intervals does not move the position of the resolved value within promiseArray . 以随机间隔解析Promises的事实不会在promiseArray 移动已解析值的位置。 What you have demonstrated is that Promise#all produces an array of values whose positions are mapped to the Promise that produced their value. 你所演示的是Promise#all生成一个值数组,其位置映射到产生其值的Promise。

See this answer for more information about the behaviour of Promise#all : 有关Promise#all行为的更多信息,请参阅此答案

All this means that the output is strictly ordered as the input as long as the input is strictly ordered (for example, an array). 所有这些意味着输出严格按输入进行排序,只要输入是严格排序的(例如,数组)。

It's because var 's scope isn't a child of for , it's a sibling. 这是因为var的范围不是for的孩子,它是兄弟姐妹。 So the loop runs once and sets i = 0 . 所以循环运行一次并设置i = 0 It then runs another 2 more times and sets i = 1 and then i = 2 . 然后再运行2次并设置i = 1然后i = 2 After this has all happened, the timeouts then run and all run the resolve function and passes in resolve( filesToLoad[2] ) . 在这一切发生之后,超时然后运行并且全部运行resolve函数并传入resolve( filesToLoad[2] ) let works correctly because the value of let i is not overridden by the following loop iterations. let正常工作,因为let i的值不会被以下循环迭代覆盖。

In short, the timeout only runs after the loop has already run 3 times and therefore passes in the same value. 简而言之,超时仅在循环已经运行3次后运行,因此传递相同的值。 I've created a jsfiddle of a working version using var . 我用var创建了一个工作版本的jsfiddle

That's the difference between let and var . 这是letvar之间的区别。 Your issue is not directly related to Promises (besides the fact that they run async). 您的问题与Promises没有直接关系(除了它们运行异步的事实)。


TL;DR TL; DR

var declarations are moved to beginning of function by interpreter, so when timeouts executed the loop has already run to end of array on the same variable. var声明由解释器移动到函数的开头,因此当执行超时时,循环已经在同一个变量上运行到数组的末尾。 let keeps the variable in scope, so each iteration of the loop used a different variable. let变量保持在范围内,因此循环的每次迭代都使用不同的变量。


Background: 背景:

Javascript has a feature called hoisting , which means that a variable or function is always declared at the beginning of it's wrapping function. Javascript有一个名为hoisting的功能,这意味着变量或函数总是在它的包装函数的开头声明。 If the coder doesn't, the interpreter moves it. 如果编码器没有,则解释器移动它。 Hence: 因此:

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

and

for (var i in array)
....

are equivalent, even though, having some reason, or other programming background, you would expect i to be scoped to the loop alone in the second case. 是等价的,即使有一些原因,或者其他编程背景,你会希望i在第二种情况下仅限于循环。

That's why 这就是为什么

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

alerts undefined instead of throwing an error. 警告undefined而不是抛出错误。 As far as the interpreter is concerned - there was a declaration. 就口译员而言 - 有一份声明。 It's like 就像是

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

Now: 现在:

ES6/ES2015 has introduced let - which behaves as our first assumption in the second example above. ES6 / ES2015引入了let - 这是我们在上面第二个例子中的第一个假设。

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

will actually throw an error. 实际上会抛出一个错误。 When reaching the alert there is no i to speak of. 到达alert ,没有i可以说。


In your case 在你的情况下

With let you went through the loop with a new variable each time, since let was defined only to scope of the loop. let你每次都使用一个新变量进行循环,因为let只被定义为循环的范围。 Each iteration is a scope, each used a different variable (though all named i in their respective scopes) 每次迭代都是一个范围,每个都使用不同的变量(尽管在各自的范围内都命名为i

When using var , you actually declared i before the loop 使用var ,实际上在循环之前声明了i

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

so it's the same variable on each iteration (or, in each scope), meaning that the loop assigned the last item to i before any of the timeouts returned. 所以它在每次迭代(或者在每个范围内)中都是相同的变量,这意味着循环在返回任何超时之前将最后一项分配给i That's why the result was 3 times the last item. 这就是为什么结果是最后一项的3倍。

These are actually two different, unrelated problems. 这实际上是两个不同的,无关的问题。

Your first observation, that the output is in the original order, rather than a random order, is due to the behaviour of Promise.all . 您的第一个观察结果是输出是按原始顺序而不是随机顺序,这是由Promise.all的行为Promise.all Promise.all returns a new promise that resolves to an array when all sub-promises have resolved, in the original order. Promise.all返回一个新的promise,当所有子promise都以原始顺序解析时,该promise将解析为数组。 You can think of it like Array.map , but for promises. 你可以把它想象成Array.map ,但是对于promises。

As stated in the Bluebird (a JS promise library) documentation : Bluebird(JS promise库)文档中所述

The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. promise的履行值是一个数组,其在原始数组的各个位置具有履行值。

Your second problem, how changing let to var changes the output to have all of the same values, is due to array scoping. 你的第二个问题是,如何更改let to var更改输出以具有所有相同的值,是由于数组作用域。 Put simply, a variable defined with var exists outside of the for loop scope. 简单地说,具有限定的可变var存在的外部 for循环范围。 This means that on each iteration, you are actually modifying the value of the pre-existing variable, as opposed to creating a new variable. 这意味着在每次迭代时,您实际上都在修改预先存在的变量的值,而不是创建新变量。 Since your code inside the Promise executes later, the variable will have the value from the last loop iteration. 由于Promise中的代码稍后执行,因此变量将具有上一次循环迭代的值。

let creates a variable that exists inside that for iteration. let创建一个内部存在一个变量for迭代。 This means that, later on in the Promise, you are accessing a variable defined in that iterations scope , which isn't overridden by the next loop iteration. 这意味着,稍后在Promise中,您将访问在该迭代范围中定义的变量,该变量不会被下一个循环迭代覆盖。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM