美文网首页
JavaScript事件循环详解(async/await、pro

JavaScript事件循环详解(async/await、pro

作者: 彩云Coding | 来源:发表于2019-02-02 09:57 被阅读0次

    1、默认的理论基础

    • 执行上下文(Execution context)

    • 函数调用栈(call stack)

    • 队列数据结构(queue)

    • Promise

    • async/await

    2、比较难懂的部分基础知识回顾

    (1)async/await与Promise的相互转化(这里主要关注的是await(thenable)这种情况下是如何转化为Promise的)

    async/await可视为 Promise 的语法糖,两者可以相互转化为彼此的写法:

    async function async1() {
        console.log('async1 start')
        await async2()
        console.log('async1 end')
    }
        
    async function async2() {
        console.log('async2')
    }
        
    async1();
    

    转化为Promise的写法:

    function async1(){
        console.log('async1 start');
        const p = async2();
        return new Promise((resolve) => {
            Promise.resolve().then(() => {
                p.then(resolve)
            })
        })
        .then(() => {
            console.log('async1 end')
        });
    }
        
    function async2(){
        console.log('async2');
        return Promise.resolve();
    }
        
    async1();
    

    这里面主要是await v的处理有点出乎意料,可能暂时你对上面的转化有点不太理解,不要紧,待我慢慢道来~~

    首先一个通用的转化步骤是:

    async function async1() {
        await fn()
    }
    

    这里的fn有可能返回的是Promise(async默认返回的就是Promise),也可能直接返回的结果(return 'something'),但是不论如何,上面的这小段代码都可以转化为:

    async function async1() {
        return new Promise((resolve) => {resolve(fn())})
    }
    

    若await后面还有别的需要执行的语句:

    async function async1() {
        await fn()
        console.log('async1 end')
    }
    

    await v 后续的代码的执行类似于传入then()中的回调:

    function async1(){
        const p = async2();
        return new Promise((resolve) => {
            resolve(fn())
        })
        .then(() => {
            console.log('async1 end')
        });
    }
    

    若fn是thenable对象,那么就还可以进一步转化为:

    function async1(){
        const p = async2();
        return new Promise((resolve) => {
            Promise.resolve().then(() => {
                p.then(resolve)
            })
        })
        .then(() => {
            console.log('async1 end')
        });
    }
    

    我相信到这,应该能够理解文章开始的那段代码的转化结果了吧,如果还是不能够理解,那么可以留言,我一定改到你明白。

    3、任务队列

    这部分网上也有很多相关的文章,有的讲的也很好,我这边就尽量把我觉得最有助于理解的部分内容说明一下:

    • 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个

    • 任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

    • macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

    • micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

    • setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。

    • 来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

    • 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。

    上面这些点基本上包含了分析事件循环时所需的基本知识,下面再先给出事件循环的一个过程:

    <u>从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。</u>

    4、小栗子(一步步的执行顺序)

    请看下面一段代码

    setTimeout(function() {
        console.log('timeout1');
    })
     
    new Promise(function(resolve) {
        console.log('promise1');
        for(var i = 0; i < 1000; i++) {
            i == 99 && resolve();
        }
        console.log('promise2');
     }).then(function() {
        console.log('then1');
     })
     
     console.log('global1');
    

    这段代码可能很多人能够给出打印的结果,但是对于其中的执行顺序是否了然于心?下面我们一步步的从任务队列、函数调用栈的角度出发,来分析这段代码:


    1.png 2.png 3.png 4.png 5.png 6.png 7.png 8.png 9.png 10.png

    经过上面这些步骤讲解,相信大家对于事件循环有了一定的认识,当然上面的这段代码是比较简单的,只是为了让大家快速的理解事件循环整个的一个机制,下面有一个综合的小测验希望大家都可以轻易的给出正确的答案。

    5、终极考验

    setTimeout(function() {
        console.log('timeout1');
    })
    async function async1(){
      console.log('async1 start')
      await async2()
      console.log('async1 end')
    }
    async function async2(){
      console.log('async2')
    }
    async1();
    
    Promise.resolve(
        new Promise(function(resolve) {
            console.log('qqq');
            resolve();
        }).then(function(){
            console.log('www');
        })
    )
    .then(()=>{
        console.log('promise resolve')
    })
    new Promise(function(resolve) {
        console.log('promise1');
        for(var i = 0; i < 1000; i++) {
            i == 99 && resolve();
        }
        new Promise(function(resolve) {
            console.log('ddd');
            resolve();
        }).then(function(){
            console.log('dcdcd');
        })
        console.log('promise2');
    }).then(function() {
        console.log('then1');
    }).then(function() {
        console.log('then2');
    })
     
    console.log('global1');
    

    大家不要对这段程序有什么恐惧心理,就按照此前的分析步骤一步步的去剖解它,就可以得出正确的答案。这里需要提醒的是async1 end的输出位置,请大家留意。

    当然如果大家还是感觉一步步的分析有什么困难的话,可以给我留言,我会补一个分析的过程,这里就不赘述了,因为比较长。。

    谢谢大家。

    相关文章

      网友评论

          本文标题:JavaScript事件循环详解(async/await、pro

          本文链接:https://www.haomeiwen.com/subject/ehqssqtx.html