[英]Can this strange behavior with let and var be explained?
以下示例代码让我困惑......
"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']
这至少让我感到困惑,但真正让我摸不着头脑的是当我改变let i to var i时,我得到以下结果....
['fileC','fileC','fileC']
作为一个最近才试图完全理解Promise而不是很久以前开始使用let的人,我真的很难过。
进一步阅读......
在得到很多很棒的答案后,我重构了这个例子来摆脱循环和我 。 对大多数人来说似乎很明显,但对我来说很有趣......
"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);
});
另外, let
是块作用域,而var
是函数作用域。
如果使用var i
:
在触发函数超时后,循环完成并且我被设置为2.所以它通过filesToLoad[2]
解决了所有setTimeout函数。
如果使用let i
:
由于它是块作用域,当函数被解析时,它会在声明setTimeOut
时记住i的状态,因此当它被解析时它使用正确的i值。
关于使用let i的输出顺序。
Promise.all(Iterable<any>|Promise<Iterable<any>> input) -> Promise
给定Iterable(数组是Iterable),或Iterable的承诺,它产生promises(或promises和values的混合),迭代Iterable中的所有值到一个数组中并返回一个在所有的时候满足的promise数组中的项目已完成。 promise的履行值是一个数组,其在原始数组的各个位置具有履行值。 如果数组中的任何promise都拒绝,则返回的promise将被拒绝并拒绝原因。
因此,无论您的承诺得到解决的顺序如何,promise.all的结果将始终具有正确顺序的承诺解析价值。
为什么使用let
和var
产生不同的结果:
在使用的原因let
生产超过所需的结果var
是,当使用let
你声明一个块范围的变量 ,使得当所述环移动到另一个迭代的值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 );
})
);
});
______
使用let
或var
会影响Promise#all
的行为吗?
在您的示例中, promiseArray
中promiseArray
的位置定义了值添加到结果数组的顺序,而不是在解析每个promiseArray
时。 以随机间隔解析Promises的事实不会在promiseArray
移动已解析值的位置。 你所演示的是Promise#all
生成一个值数组,其位置映射到产生其值的Promise。
有关Promise#all
行为的更多信息,请参阅此答案 :
所有这些意味着输出严格按输入进行排序,只要输入是严格排序的(例如,数组)。
这是因为var
的范围不是for
的孩子,它是兄弟姐妹。 所以循环运行一次并设置i = 0
。 然后再运行2次并设置i = 1
然后i = 2
。 在这一切发生之后,超时然后运行并且全部运行resolve函数并传入resolve( filesToLoad[2] )
。 let
正常工作,因为let i
的值不会被以下循环迭代覆盖。
简而言之,超时仅在循环已经运行3次后运行,因此传递相同的值。 我用var
创建了一个工作版本的jsfiddle 。
这是let
和var
之间的区别。 您的问题与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倍。
这实际上是两个不同的,无关的问题。
您的第一个观察结果是输出是按原始顺序而不是随机顺序,这是由Promise.all
的行为Promise.all
。 Promise.all
返回一个新的promise,当所有子promise都以原始顺序解析时,该promise将解析为数组。 你可以把它想象成Array.map
,但是对于promises。
promise的履行值是一个数组,其在原始数组的各个位置具有履行值。
你的第二个问题是,如何更改let
to var
更改输出以具有所有相同的值,是由于数组作用域。 简单地说,具有限定的可变var
存在的外部 for
循环范围。 这意味着在每次迭代时,您实际上都在修改预先存在的变量的值,而不是创建新变量。 由于Promise中的代码稍后执行,因此变量将具有上一次循环迭代的值。
let
创建一个内部存在一个变量for
迭代。 这意味着,稍后在Promise中,您将访问在该迭代范围中定义的变量,该变量不会被下一个循环迭代覆盖。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.