美文网首页程序员web之路让前端飞
【十一】JavaScript执行(一):Promise里的代码为

【十一】JavaScript执行(一):Promise里的代码为

作者: alanwhy | 来源:发表于2019-02-27 21:20 被阅读3次

    首先我们要形成一个感性的认知:一个JS引擎会常驻于内存中,等待着宿主把JS代码或者函数传递给它执行。

    在ES3或者更早的版本中,JS本身是没有异步执行代码的能力的,也就是说,宿主环境传递给JS引擎一段代码,引擎就把代码直接顺次执行了,这个任务也就是宿主发起的任务。

    但是,ES5之后,JS引入了Promise,如此而来,JS引擎本身也可以发起任务了

    那么采纳JSC引擎的术语,我们把宿主发起的任务叫宏观任务,把JS引擎发起的任务叫微观任务。

    宏观和微观任务

    JS引擎等待宿主环境分配宏观任务,在node术语中,叫做事件循环

    整个循环做的事情基本上就是反复“等待-执行”,当然实际的代码中并没有那么简单,还有要判断循环是否结束、微观任务队列等逻辑。

    在宏观任务中,JS的Promise还会产生异步代码,JS必须保证这些异步代码在一个宏观任务中完成,所以每个宏观任务又包含了一个微观任务队列:


    image.png

    有了宏观任务和微观任务机制,就可以实现JS引擎级和宿主机的任务了,例如:promise永远在队列尾部添加微观任务。setTimeout等宿主API,则会添加宏观任务。

    Promise

    promise是JS语言提供的一种标准化的异步管理方式,总体思想是,需要进行io、等待或者其他异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个promise兑现(通过promise的then方法回调)

    promise的基本用法:

    // 等候传入参数指定的时长
        function sleep(duration) {
            return new Promise(function(resolve, reject) {
                setTimeout(resolve,duration);
            })
        }
        sleep(1000).then(()=> console.log("finished"));
    

    promise的then回调是一个异步的执行过程,下面我们就来研究一下promise函数中的执行顺序,例如:

        var r = new Promise(function(resolve, reject){
            console.log("a");
            resolve()
        });
        r.then(() => console.log("c"));
        console.log("b")
    

    输出结果为a b c。在进入console.log("b")之前,r已经得到了resolve,但是promise的resolve始终是异步操作,所以c无法出现在b之前。

    在看一个:

        var r = new Promise(function(resolve, reject){
            console.log("a");
            resolve()
        });
        setTimeout(()=>console.log("d"), 0)
        r.then(() => console.log("c"));
        console.log("b")
    

    输出结果为a b c d。因为promise是JS引擎内部的微任务,而setTimeout是浏览器API,是宏任务。

    为了便于理解微任务始终先于宏任务,在看一个例子:

        setTimeout(()=>console.log("d"), 0)
        var r = new Promise(function(resolve, reject){
            resolve()
        });
        r.then(() => { 
            var begin = Date.now();
            while(Date.now() - begin < 1000);
            console.log("c1") 
            new Promise(function(resolve, reject){
                resolve()
            }).then(() => console.log("c2"))
        });
    

    执行一个耗时1s的promise。可以确保c2是在d之后被添加到任务队列。

    最后总结下如何分析异步执行的顺序:

    • 首先分析有多少个宏任务
    • 每个宏任务中,有多少个微任务
    • 根据调用次序,确定宏任务中微任务的执行次序
    • 根据宏任务的触发规则和调用次序,确定宏任务的执行次序
    • 确定整个顺序

    值得一提,promise是JS中的一个定义,但是实际编写代码时,它似乎并不比毁掉的方式书写简单,但是从ES6开始,我们有了async/await,这个语法改进跟promise配合,能够有效地改善代码结构

    async/await

    async/await是ES6新加入的特性,提供了for、if等代码结构来编写异步的方式。运行基础是promise。

    async函数必定返回promise,特征是在function关键字前加上async关键字,这样就定义了一个async函数,接着可以使用await来等待一个promise

    function sleep(duration) {
        return new Promise(function(resolve, reject) {
            setTimeout(resolve,duration);
        })
    }
    async function foo(){
        console.log("a")
        await sleep(2000)
        console.log("b")
    }
    

    async函数的强大在于它可以嵌套使用。

    function sleep(duration) {
        return new Promise(function(resolve, reject) {
            setTimeout(resolve,duration);
        })
    }
    async function foo(name){
        await sleep(2000)
        console.log(name)
    }
    async function foo2(){
        await foo("a");
        await foo("b");
    }
    

    小练习

    需求:实现一个红绿灯,圆形的div按照绿色3s,黄色1s,红色2s循环改变背景色。

    代码实现:

    const lightEle = document.getElementById('traffic-light');
    function changeTrafficLight(color, duration) {
      return new Promise(function(resolve, reject) {
        lightEle.style.background = color;
        setTimeout(resolve, duration);
      })
    }
    
    async function trafficScheduler() {
      await changeTrafficLight('green', 3000);
      await changeTrafficLight('yellow', 1000);
      await changeTrafficLight('red', 2000);
      trafficScheduler();
    }
    
    trafficScheduler();
    

    参考原文: JavaScript执行(一):Promise里的代码为什么比setTimeout先执行?

    相关文章

      网友评论

        本文标题:【十一】JavaScript执行(一):Promise里的代码为

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