for循环里异步操作问题小结

4,853 阅读1分钟

假如我们想实现每隔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()

扩展链接: