用Promise的例子解释事件循环EventLoop

关于事件循环的定义参考这篇文章:事件循环机制EventLoop

大家准备好了吗?我要开始复制粘贴了

不不不,我要开始整理精华了(毕竟,我觉得博客都有遗漏之处,可能作者理解了但是忘了写)

强迫症的我告诉自己必须再详细解释一番

关于定义,大家可以反复阅读上面的一片文章

MacroTask(宏任务):

script全部代码、
setTimeout、
setInterval、
setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、
I/O、
UI Rendering

MicroTask(微任务):

Process.nextTick(Node独有)、
Promise的callback回调(注意Promise的函数直接执行,算script里面的代码)、
Object.observe(废弃)、
MutationObserver(具体使用方式查看这里)

偷图一张如下:

å¨è¿éæå¥å¾çæè¿°

例子开始:

    console.log(1); // script代码
    setTimeout(function () { // 异步模块,执行完放到专有的宏任务队列
        console.log(2);
    });
    new Promise(function(resolve,reject){ // Promise里的的函数直接执行,算是script代码
        console.log(3);
        resolve(4); // 只有执行resolve或者reject才会有回调,不执行这个就不会执行then里面的回调函数
    }).then(function(val){
        console.log(val); // 微任务
    })
    console.log(5); // script代码

执行结果:

过程讲解:

1.首先执行主线程这个宏任务,从上到下执行,遇到console.log(1); (script代码,打印1)
2.遇到setTimeout,把它丢给异步模块定时器处理,然后继续往下执行,并不会阻塞定时器,而此处定时器线程会在,主线程执行完后,把回调函数放入宏任务队列,注意宏任务队列可以有很多个,但是微任务队列只有一个。


3.遇到new Promise,直接执行,console.log(3)(里面算script代码,打印3)
4.执行resolve方法,最终会回调then里面的函数, 属于微任务,则把回调函数放入微任务队列
5.遇到console.log(5) 打印 ‘5’ 出来
6.一个宏任务执行完后(这里就是执行了全部script代码,算一个宏任务),会执行所有待执行的微任务队列里面的所有任务,所以打印 4 。

7.检查是否需要渲染UI,这是宏任务,也是上面那张图的示意要求,一轮事件循环结束了
第一轮循环里的宏任务和微任务都会被移除出任务队列,接下来开启第二轮循环

8.首先查找是否有宏任务,由于setTimeout 的回调被放入了宏任务队列,这里会执行回调函数的代码,打印了 ‘2’ 出来
9.接着查找是否有微任务,发现没有微任务,检查是否需要渲染UI,然后本轮循环结束
接下来会重复上面的步骤,这就是event loop 了。后续当我们触发点击事件,有回调函数的话,回调函数也会被放入宏任务队列,一旦队列里重新有了任务,就会被执行。

关于promise的resolve和then的问题

promise是立即执行的,它创建的时候就会执行,不存在将promise推入微任务中的说法;

resolve()是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它;

promise调用then的前提是promise的状态为fullfilled;

只有promise调用then的时候,then里面的函数才会被推入微任务中;

大家是否理解了呢?我们来看一个复杂一点的例子,把上面的知识串起来了

(答应我,上面没看懂就别往下看了,否则就像下面这张图)

            console.log('start'); // script代码
 
            var intervalA = setInterval(() => { // 放到异步处理模块
                console.log('intervalA');
            }, 0);
 
            setTimeout(() => { // 放到异步处理模块
                console.log('timeout');
 
                clearInterval(intervalA);
            }, 0);
 
            var intervalB = setInterval(() => { // 放到异步处理模块
                console.log('intervalB');
            }, 0);
 
            var intervalC = setInterval(() => { // 放到异步处理模块
                console.log('intervalC');
            }, 0);
 
            new Promise((resolve, reject) => { // 直接执行
                console.log('promise');
 
                for (var i = 0; i < 10000; ++i) {
                    i === 9999 && resolve(); // 满足条件执行一次
                }
 
                console.log('promise after for-loop');
            }).then(() => { // 上面i为9999的时候执行了resolve,所以会将这里加入微任务队列
                console.log('promise1');
            }).then(() => {
                console.log('promise2');
 
                clearInterval(intervalB);
            });
 
            new Promise((resolve, reject) => { // 直接执行
                setTimeout(() => { // 放到异步处理模块,在这之前resolve没执行,后面then回调函数不会放入微任务
                    console.log('promise in timeout');
                    resolve();
                });
 
                console.log('promise after timeout');
            }).then(() => {
                console.log('promise4');
            }).then(() => {
                console.log('promise5');
 
                clearInterval(intervalC);
            });
 
            Promise.resolve().then(() => { // 放入微任务
                console.log('promise3');
            });
 
            console.log('end');

运行结果:

解析:

第一轮,执行全部属于script的代码,打印

start
promise
promise after for-loop
promise after timeout
end

到这里,宏任务执行了一个(全部script代码),接下来执行目前微任务队列的所有任务。

目前的微任务队列如下:

promise1   --->队头
promise3    --->队尾

外部的第二个 then 的注册,需要等待外部的第一个 then 的同步代码执行完成。所以这里没有promise2

执行了第一个then之后,打印promise1,返回Promise对象,队头出队,再次注册第二个then,此时微任务队列任务如下:

promise3    ---->队头
promise2    ---->队尾,执行promise2的时候clearInterval(intervalB);清除了B定时器

然后依次执行完微任务,检查是否需要渲染。

接下来看第二个宏任务队列,这里全部是异步模块定时器处理后放入的任务

执行宏任务,打印intervalA,检查微任务队列为空,检查是否渲染

执行宏任务,打印timeout,检查微任务队列为空,检查是否渲染

执行宏任务,打印intervalC,检查微任务队列为空,检查是否渲染(intervalB刚刚被取消了所以不执行)

执行宏任务,打印promise in timeout,接着执行resolve方法,表示promise的状态为fullfilled,promise调用then的前提是promise的状态为fullfilled,所以此时then里面的函数推入微任务队列。宏任务执行完毕,

执行微任务打印promise4,注册第二个then,进入微任务队列,再执行微任务promise5,任务执行完毕出队,然后微任务队列空,检查是否需要渲染。

接着最后检查宏任务队列为空,检查微任务队列为空,是否需要渲染,取消事件订阅,该事件的循环结束。

看到这里,相信大家对于事件循环EventLoop有了更深刻的理解,接下来,请大家看到文章开头推荐的文章再读几遍即可。

参考文章:

https://blog.csdn.net/tzllxya/article/details/92674042

https://blog.csdn.net/qq_39539687/article/details/100557701

===============Talk is cheap, show me the code================

发布了224 篇原创文章 · 获赞 983 · 访问量 91万+

猜你喜欢

转载自blog.csdn.net/qq_34115899/article/details/104086777