以下示例代码让我感到困惑......
"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);
});
答案 0 :(得分:4)
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)
为什么使用let
和var
会产生不同的结果:
使用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 );
})
);
});
______
使用let
或var
会影响Promise#all
的行为吗?
没有。在您的示例中,promiseArray
中Promise的位置定义了值添加到结果数组的顺序,而不是在解析每个Promise时。以随机间隔解析Promise的事实不会移动解析值在promiseArray
内的位置。您所演示的是Promise#all
生成一个值数组,其位置映射到产生其值的Promise。
有关Promise#all
所有这些意味着只要输入严格排序(例如,数组),输出就会严格按输入排序。
答案 2 :(得分:1)
因为var
的范围不是for
的孩子,所以它是兄弟姐妹。所以循环运行一次并设置i = 0
。然后再运行2次并设置i = 1
然后i = 2
。完成所有操作后,超时然后运行并运行resolve函数并传入resolve( filesToLoad[2] )
。 let
正常工作,因为以下循环迭代不会覆盖let i
的值。
简而言之,超时仅在循环已经运行3次后运行,因此传递相同的值。我使用var
创建了jsfiddle工作版本。
答案 3 :(得分:0)
这是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倍。
答案 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中,您将访问在该迭代范围中定义的变量,该变量不会被下一个循环迭代覆盖。