假如我们想实现每隔1s输出0-4的值,来看下以下代码的执行结果:
var arr = [1,2,3,4,5]
for(var i= 0;i< arr.length; i++){
setTimeout(function(){
console.log(i)
},1000)
}
很明显,这个循环我们是想得到 0,1,2,3,4 的结果,但是最后会打印出5个5,这是因为JavaScript会将异步任务放在一个队列里面,等所有的同步任务执行完成之后才会执行异步任务。所以在以上代码中遇到异步任务setTimeout后继续执行for循环,由于只有一个全局作用域,每次 for 循环修改的都是同一个i,等for循环执行完毕即 i 等于5时,开始执行异步任务setTimeout,打印此时 i 的值 5 。那么,要怎么解决这个问题呢。
1.使用es6新增的 let 来声明变量,let可以声明一个块级作用域,在每个块级作用域里面的 i 都是属于该作用域的,即每个作用域都有它自己的 i,所以可以得到想要的结果。
var arr = [1,2,3,4,5]
for(let i= 0;i< arr.length; i++){
setTimeout(function(){
console.log(i) //0,1,2,3,4
},1000)
}
2.使用自调用函数。将 i 作为函数的参数传递进去,函数执行有自己的函数作用域,也可以达到效果。
var arr = [1,2,3,4,5]
for(var i= 0;i< arr.length; i++){
(function(i){
setTimeout(function(){
console.log(i) //0,1,2,3,4
},1000)
})(i)
}
注意以上两种写法,我们解决了输出0-4的值,但打印操作几乎是在同一时间完成,setTimeout定时根本就没有起作用。正确的实现如下:
- 使用递归函数
function test(param) {
if (param < 5) {
console.log("index is :", param);
setTimeout(function() {
test(param + 1);
}, 1000)
}
}
test(0);
// 递归实现倒计时
function showTime(count) {
console.log("count is : ", count);
if (count == 0) {
console.log("All is Done!");
} else {
count -= 1;
setTimeout(function() {
showTime(count);
}, 1000);
}
}
showTime(20);
4.async、await实现
var asyncFunc = function(arr, i) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
arr.push(i);
console.log("index is : ", i);
resolve();
}, 1000);
});
}
var test = async function() {
var arr = [];
for (var i = 0; i < 5; i++) {
await asyncFunc(arr, i);
}
console.log(arr);
}
test();
如何确保循环的所有异步操作完成之后执行某个其他操作?
- 设置一个flag,在每个异步操作中对flag进行检测
let arr = [1, 2, 3, 4, 5]
var asyncFunc = function(arr, i) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("index is : ", i);
resolve(i);
}, 1000);
});
}
let flag = 0;
var test = async function() {
for (var i = 0; i < arr.length; i++) {
flag++
await asyncFunc(arr, i).then(res => {
if (flag == arr.length) {
console.log('all is done! index== ', res)
}
})
}
}
test();
- 将所有的循环放在一个promise中,使用then处理
let arr = [1, 2, 3, 4, 5]
var asyncFunc = function(arr, i) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("index is : ", i);
resolve(i);
}, 1000);
});
}
new Promise(function(resolve) {
resolve()
}).then(async res => {
for (var i = 0; i < arr.length; i++) {
await asyncFunc(arr, i).then(res => {})
}
}).then(() => {
// your code
console.log('all is done')
})
循环中的下一步操作依赖于前一步的操作,如何解决?
- 使用递归,在异步操作完成之后调用下一次异步操作
var arr = [1,2,3,4,5];
(function loop(index) {
setTimeout(function(){//用setTimeout模拟异步函数
console.log(arr[index]);
if (++index<arr.length) {
loop(index);
} else {
console.log("全部执行完毕");
}
}, 1000);
})(0);
- 使用async和await
var arr = [1, 2, 3, 4, 5];
var asyncFunc = function(arr, i) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("index is : ", i);
resolve(i);
}, 1000);
});
}
async function loop() {
for (let i = 0; i < arr.length; i++) {
await asyncFunc(arr,i).then(res=>{})
}
console.log('all is done')
}
loop()