• 【学习笔记】JS经典异步操作,从闭包到async/await


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

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

     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,4
    5     }, 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,4
    4     },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)  //循环结束输出5
    10 },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);
    })();
  • 相关阅读:
    基于nginx的rtmp的服务器(nginx-rtmp-module)
    基于nginx的HLS简单服务器搭建
    HLS(HTTP Live Streaming)协议之m3u8文件生成方式
    基于live555的一个简单RTSP服务器
    基于webrtc的多人视频会话的demo运行程序
    写给小白的Python之019:面向对象-类方法、静态方法
    写给小白的Python之018:面向对象-私有成员、@property
    写给小白的Python之017:面向对象-封装、继承、多态
    写给小白的Python之016:面向对象-魔法方法
    写给小白的Python之015:面向对象-类和对象
  • 原文地址:https://www.cnblogs.com/youma/p/10481727.html
Copyright © 2020-2023  润新知