写在前面的话
setTimeout是我们学习
JavaScript
基础都必须面对的问题,也许当时你搞懂了,但是过一段时间就又忘记了。最近事情不多,我将梳理出for + setTimeout
相关的知识点,以及使用Promise
、async/await
来加深对异步、同步的理解
setTimeout
直接进入正题:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
也许我们希望输出的是
1,2,3,4,5
,但实际情况是我们输出了5,5,5,5,5
,这到底是为什么呢?
由JS的运行机制中我们得知:当线程中没有任何同步代码的前提下才会执行异步代码。我们的for循环时同步的,但是setTimeout是异步的,由此就造成了输出5,5,5,5,5
这种情况。
通过一顿胡乱分析我们得出假设:只要保证里面的也变成同步的是不是就可以了呢?
方式①
for(var i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
}, i*1000)
}
这里使用的是最简单的方式:动态的改变延迟的时间。
因为for
循环时同步的,而setTimeout
是异步的,所以会先把for
循环执行完毕,然后再执行内部的setTimeout
,所以输出结果为每隔1
秒输出一个5
方式②
for (var i = 0; i < 5; i++) {
(function(j){
setTimeout(function (){
console.log(j);
},1000);
})(i);
}
我们通过设置一个立即执行函数
(IIFE)
,这样就能保证里面和外面的是同步执行的。
方式③
function output(i){
setTimeout(function(){
console.log(i);
}, 1000)
}
for(var i = 0; i < 5; i++){
output(i);
}
这里其实和第一种方法类似,只不过我们把这个函数单独拿出来,并把
index
值当做函数的参数来传递
方式④
for(let i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
}, 1000)
}
let
是ES6
语法,for
循环代码块构成一个作用域,里面的内容引用了上层作用域的变量i
,并最终形成五个闭包,而for
使用var
时,还是ES5
的写法,for
代码块没有形成作用域,所以里面的function
不构成闭包。同理我们的方式①
和方式②
都形成了闭包函数。
如果我们需要最后一个延迟5
秒,其余的都是延迟1
秒,我们就可以用到ES6
的语法--Promise,下面我们用Promise实现这一情况
方式⑤
const task = [];
const output = (i) => new Promise(function(resolve, reject){
setTimeout(function(){
console.log(i);
resolve();
}, i*1000)
})
for (var i = 0; i<5; i++){
task.push(output(i));
}
Promise.all(task).then(() => {
setTimeout(() => {
console.log(i);
}, 5000);
})
这里采用了
Promise
解决异步的方式,在ES7
中解决异步还有async/await的方式
方式⑥
const sleep = (timeount) => new Promise((resolve) => {
setTimeout(resolve, timeount);
});
Func = async () => {
for(var i = 0; i<5; i++){
await sleep(1000);
console.log(i);
}
await sleep(5000);
console.log(i);
}
this.Func();
async
表示这是一个async
函数,await
只能用在这个函数里面;
await
表示在这里等待promise
返回结果了,再继续执行;
await
后面跟着的应该是一个promise
对象,否则没有同步效果