继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

JS经典异步操作,从闭包到async/await

蝴蝶不菲
关注TA
已关注
手记 424
粉丝 81
获赞 385

参考文献:王仕军——知乎专栏前端周刊

感谢作者的热心总结,本文在理解的基础上,根据自己能力水平作了一点小小的修改,在加深自己印象的同时也希望能和各位共同进步...

 1. 异步与for循环

抛出一个问题,下面的代码输出什么?

复制代码

1 for (var i = 0; i < 5; i++) {2     setTimeout(function() {3         console.log(i);4     }, 1000);5 }6 console.log(i);

复制代码

相信绝大部分同学都能答的上,它的正确答案是立即输出5,过1秒钟后一次性输出5个5,这是一个典型的JS异步问题,首先for循环的循环体是一个异步函数,并且变量i添加到全局环境中,所以立即输出一个5,一秒钟后,异步函数setTimeout输出五次循环的结果,打印5 5 5 5 5(没有时间间隔)。

2. 闭包

现在我们把需求改一下,希望输出的结果是5 ->0,1,2,3,4, 应该怎么修改代码呢?

很明显我们可以用闭包创建一个不销毁的作用域,保证变量i每次都能正常输出。 

复制代码

1 for(var i=0;i<5;i++){2     (function(j)3         {setTimeout(() => {4             console.log(j); //过一秒输出 0,1,2,3,45     }, 1000)})(i)6 }7 console.log(i);  //立即输出5

复制代码

因为立即执行会造成内存泄漏不建立大量使用,那么我们还可以这样

复制代码

var output = function(i){
    setTimeout(()=>{
        console.log(i);  // 过1秒输出0,1,2,3,4
    },1000)
}for(var i=0;i<5;i++){
    output(i);
}
console.log(i);  //立即输出5

复制代码

JS基本类型是按值传递的,我们给函数output传了一个参数,所以它就会保存每次循环的实参,所以得到的结果和采用立即执行函数的结果一致。

3. ES6语法

当然我们也可以使用ES6的语法,还记得for循环中使用let声明可以有效阻止变量添加到全局作用域吗?

复制代码

1 for(let i=0;i<5;i++){2     setTimeout(()=>{3         console.log(i)  //一秒钟后同时输出0,1,2,3,44     },1000)5 }
6 console.log(i) //这一行会报错,因为i只存在于for循环中

复制代码

for循环中let声明有一个特点,i只在本轮循环中有效,所以每循环一个i其实都是新变量,而javaScript引擎内部会记住上一次循环的值,初始化变量i时,就在上轮循环基础上计算。

现在我们又改一下需求,希望先输出0,之后每隔一秒依次输出1,2,3,4,循环结束再输出5。

很容易想到,我们可以再增加一个定时器,定时器的时间和循环次数有关

复制代码

 1 for(var i=0;i<5;i++){ 2     (function(j){ 3         setTimeout(() => { 4             console.log(j)  //立即输出0,之后每隔1秒输出1,2,3,4 5         }, 1000*j); 6     })(i) 7 } 8 setTimeout(()=>{ 9     console.log(i)  //循环结束输出510 },1000*i)

复制代码

这虽然也是个办法,但代码写着确实不太好看,异步操作我们首先就要想到Promise对象,尝试用Promise对象来改写

复制代码

let tasks = [];for(var i=0;i<5;i++){
    ((j)=>{
        tasks.push(new Promise(
            (resolve)=>{
                setTimeout(() => {
                    console.log(j);
                    resolve();       //执行resolve,返回Promise处理结果
                }, 1000*j);
            }
        ))
    })(i)
}
Promise.all(tasks).then(()=>{
    setTimeout(() => {
        console.log(i);    
    }, 1000);                //只要把时间设为1秒
})

复制代码

Promise.all返回一个Promise实例,在tasks的promise状态为resolved时回调完成,这就是我们必须要在循环体中resolve()的原因。

我们将上面的代码重新排版,让其颗粒度更小,模块化更好,简洁明了

复制代码

let tasks = [];   //存放一个异步操作
let output = (i)=>  //返回一个Promise对象    new Promise((resolve)=>{
        setTimeout(() => {
            console.log(i);
            resolve();
        }, 1000*i);
    })for(var i=0;i<5;i++){     //生成全部的异步操作
    tasks.push(output(i))
}
Promise.all(tasks).then(()=>{   //tasks里的promise对象都为resolved调用then链的第一个回调函数
    setTimeout(() => {
        console.log(i)
    }, 1000);
})

复制代码

4. async/await优化

上次写了一篇关于async和await优化then链的博客,感兴趣的可以看看:深入理解async/await

对于then链,我们是可以进一步优化的:

复制代码

let sleep = (timeountMS) => new Promise((resolve) => {
    setTimeout(resolve, timeountMS);
});

(async () => {  // 声明即执行的 async 函数表达式
    for (var i = 0; i < 5; i++) {
        await sleep(1000);
        console.log(i);
    }
    await sleep(1000);
    console.log(i);
})();

复制代码

作者:唐吉sir

原文出处:https://www.cnblogs.com/youma/p/10481727.html  

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP