[JavaScript]js EventLoop And Async

update 2019-07-03html

What

js事件循环前端

先来看一张图(这张图来自于http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html)node

js事件循环

再来看一张图(这张图来自于https://www.cnblogs.com/hity-tt/p/6733062.html)
web


从一开始,js有一个执行栈(stack)中存放了该页面全部的代码即该页面主线程,当执行遇到异步操做(async)时,浏览器的webcore模块(如network,timer,domBinding模块等)将其放置到一个幕后线程中等待,而后浏览器接着执行主线程,当幕后线程中的代码准备好了(如按期器时间到了,请求的响应来了),该线程就会把这个函数的回调放到taskQueue(任务队列)中等待,当主线程的执行栈所有执行完毕时(全部同步操做所有执行完毕),主线程对任务对列进行检测是否有任务要执行,若是有,就把该任务放到执行栈中进行,若是没有,就保持等待(循环检测)任务到来。promise

上述的过程,就是事件循环(EventLoop)。浏览器

Why need EventLoop 

由于web前端端用户的丰富交互性问题,js这边采起了单线程来应对多个交互产生时的程序操做复杂性问题。因为单线程的出现,当同步操做出现长时间等待时就会出现线程阻塞,为了解决阻塞,出现了异步操做(变相多线程)来解决线程阻塞。bash

异步操做就是经过EventLoop的实现的数据结构

SimpleLook

事件循环三步看

  1.  判断js代码是否为异步,异步则放置该代码到幕后线程,同步则继续执行
  2.  幕后线程中的函数回调被触发时将该代码推入任务队列
  3.  同步任务执行完毕后,检测事件队列,将任务队列尾部函数回调推入执行栈执行
  4.  以上三步循环执行,即EventLoop

两步来看

  1. 执行主线程,同步操做,遇到异步压入事件表随后(该代码在触发响应时)压入队列
  2. 主线程代码执行完毕后,无限循环(?)检测异步事件队列,有则进入1执行

一句话讲清楚

主线程执行完毕后,检测任务队列有则取出一个插入主线程,重复该动做。多线程

任务优先级

如上图,任务队列中的任务分为两种,对应大型工做场景和微处理场景,即对应的传说中的Macrotask和Microtask。dom

what

顾名思义,即多个任务同时出现时的执行优先等级。

Macrotask: setTimeout,setInterval,setImmediate,用户交互操做/UI渲染(请求回调执行)etc

Microtask:  process.nextTick(nodejs)>promise.then etc

why

当任务队列中有多个任务时,事件优先级就须要出现了。

How

大体多任务时处理步骤以下四小步(来自网上,感受该步骤描述有问题,我后续修复了)

1. 抽取任务中全部MicroTask到2

2. 从Microtask队列中取队首(在队列时间最长)的任务去执行栈中执行(仅仅一个),执行完后进入下一步3

3. 检查Microtask队列是否为空,空则到4,不然到2

4. 检测Macrotask中的MicroTask,非空1, 空5

5. 从Macrotask队列中取队尾(在队列时间最长)的任务进去执行栈执行,执行完后,跳到4


Important

在一个循环中,或者说在处理完执行栈后,在处理任务队列时,永远先处理微任务队列。换句话说,若是在当前循环中一个微任务中出现了一个异步微任务,那么该微任务属于本次循环,会被推入微任务队列中!你仍然须要优先将while (microtask.chain.length) { // }执行下去。直到微任务处理完毕。再执行宏任务。至于同次循环中多个微任务执行时机,视推入时机而定!


简而言之:同步环境执行 -> 事件循环1(microtask queue的All)-> 事件循环2(macrotask queue中的一个) -> 事件循环1(microtask queue的All)-> 事件循环2(macrotask queue中的一个)...

通俗点说任务队列中的任务所执行的顺序:

开始以前简单讲下队列数据结构,一种先进先出的数据结构,能够理解为一个双开的口径为一个豆子大小的竹筒,队列中的数据就像豆子,豆子们排着队从竹筒的一头倒进去再从另外一头倒出来,就是这么理解。

  • 首先清楚一个概念,用大小豆子类来比喻优先级,宏任务表明着大豆子子类,微任务表明着小豆子子类。
  • 系统将任务队列的任务豆子们按照在任务队列中的前后顺序(豆子们被依次塞入竹筒,越靠近出口处序号越大)升序写上编号后从竹筛中筛出来。
  • 而后先将小豆子们按照编号进行升序排序后倒入竹筒中,小豆子们被依次倒出到执行栈,一次倒一个(最后一个出来的小豆子序号最小咯)。
  • 再将大豆子们按照编号升序排序后倒入竹筒中,大豆子们被依次倒出到执行栈。
  • 这样就完成了一次倒豆子!(任务队列执行)
  • 若是豆子们中出现了新的豆子(新的异步任务)怎么办呢?(从这一条能够不看,看上面有排序的处理步骤就行了)
  • 若是是小豆子,那么将持续的将小豆子先倒光再去倒大豆子。
  • 接下来再去按上面的顺序处理大豆子。

看完上面后来理解下下面的代码(基于掘金小美娜娜登峰造极版修改,添加Promise链式调用第二层then)顺便理解下上面说的微任务处理器的清空处理是什么意思,理解后相信对事件循环理解更深了。

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

async  function async2() {
    console.log( 'async2');
}

console.log("script start");

setTimeout(function () {
    console.log("settimeout");
},0);

async1();

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
}).then(()=>{console.log('promise3')});
console.log('script end'); /* 
script start
VM955:2 async1 start
VM955:8 async2
VM955:20 promise1
VM955:25 script end
VM955:4 async1 end
VM955:23 promise2
VM955:24 promise3
undefined
VM955:14 settimeout
*/复制代码

我知道你看到这有点晕,回过头去看看Important吧。

原理的话,我这两天会把Promise以及用于链式的then方法实现一下,能够来看看而后再看上面这个例子。

Special


Demo

demo来源于网上,作抽象概念具现化理解用

setTimeout(function(){console.log('111')},0);
new Promise(function(resolve,reject){
console.log("2222");//此处尚未执行异步操做,执行异步操做及执行回调函数,在promise中即then中的回调
resolve();
}).then(function(){console.log('3333')})
console.log("44444");
//输出
// 2222
// 44444//上面的两个输出属于同步操做
// 3333//promise加入到队列的优先级高于setTimeout
//111

嵌套微任务

new Promise(function(resolve,reject){

resolve();
}).then( function (){
console.log( "111" );
return new Promise( function (resolve,reject){
resolve();
})
}).then( function (){ console.log( "222" );})
new Promise( function (resolve,reject){
resolve();
}).then( function (){ console.log( "33333" );})
//输出
111 33333 222


参考

Eventloop不可怕,可怕的是赶上Promise

相关文章
相关标签/搜索