EventLoop - 遇上Promose

当EventLoop遇上Promise后,会有一些弯绕不过来,以下题目作为参考,题目参考自https://juejin.im/post/5c9a43175188252d876e5903,特整理一版,以便翻阅。

1. 得心应手版

考点:EventLoop中的执行顺序,宏任务 / 微任务的区别。

setTimeout(()=>{
   console.log(1) 
},0)
Promise.resolve().then(()=>{
   console.log(2) 
})
console.log(3) 

// 输出 3 2 1  
//如果不会这个  去网上重新学习吧

这里就有一个版本问题,是参考的笔者所指出的,这也是我看了一些文章后疑惑的点,被笔者讲清楚了,很赞,贴出来,防止以后忘记。
有时候会有版本是宏任务 ->微任务 -> 宏任务,在这里笔者需要讲清楚一个概念,以免混淆。这里有个main script(主代码)的概念,就是一开始执行的代码(代码总要有开始执行的时候对吧,不然宏任务和微任务的队列哪里来的),这里被定义为了宏任务(笔者喜欢将main script的概念单独拎出来,不和两个任务队列混在一起),然后根据main script中产生的微任务队列和宏任务队列,分别清空,这个时候是先清空微任务的队列,再去清空宏任务的队列。

2. 游刃有余版

setTimeout(()=>{ // s1
    console.log(1)
},0)
let a=new Promise((resolve)=>{ 
    console.log(2)
    resolve()
}).then(()=>{ // p1
    console.log(3)
}).then(()=>{ // p2
    console.log(4)
})
console.log(5)

// 输出 2 5 3 4 1

步骤:
1. 主代码执行
宏任务队列 = [s1]
console.log(2)  ======================> 2
微任务队列 = [p1]
console.log(5)  ======================> 5

2. 清空微任务
微任务队列 = []
console.log(3)  ======================> 3
微任务队列 = [p2]

3. 清空微任务
微任务队列 = []
console.log(4)  ======================> 4

4. 拿出第一个宏任务 s1
宏任务队列 = []
console.log(1)  ======================> 1

注意:
这个要从Promise的实现来说,Promise的executor是一个同步函数,即非异步,立即执行的一个函数,因此他应该是和当前的任务一起执行的。而Promise的链式调用then,每次都会在内部生成一个新的Promise,然后执行then,在执行的过程中不断向微任务(microtask)推入新的函数,因此直至微任务(microtask)的队列清空后才会执行下一波的macrotask。

3. 炉火纯青版

这一个版本是上一个版本的进化版本,上一个版本的promise的then函数并未返回一个promise,如果在promise的then中创建一个promise,那么结果该如何呢?

new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{  // p1
    console.log("then11")
    new Promise((resolve,reject)=>{ 
        console.log("promise2")
        resolve()
    }).then(()=>{ // p2
        console.log("then21")
    }).then(()=>{ // p3
        console.log("then23")
    })
}).then(()=>{ // p4
    console.log("then12")
})

// 输出  
// promise1
// then11
// promise2
// then21
// then12
// then23

步骤
1. 主代码执行
console.log("promise1")  ======================> promise1
微任务队列 = [p1]

2. 清空微任务队列
微任务队列 = []
console.log("then11")  ======================> then11
console.log("promise2")  ======================> promise2
微任务队列 = [p2 , p4]

3. 清空微任务队列
微任务队列 = [p4]
console.log("then21")  ======================> then21
微任务队列 = []
console.log("then12")  ======================> then12
微任务队列 = [p3]

4. 清空微任务队列
微任务队列 = []
console.log("then23")  ======================> then23

3.1 变异版本

new Promise((resolve,reject)=>{
  console.log("promise1")
  resolve()
}).then(()=>{ // p1
  console.log("then11")
  return new Promise((resolve,reject)=>{
      console.log("promise2")
      resolve()
  }).then(()=>{ // p2
      console.log("then21")
  }).then(()=>{ // p3
      console.log("then23")
  })
}).then(()=>{ // p4
  console.log("then12")
})

// 输出  
// promise1
// then11
// promise2
// then21
// then23
// then12

注意  return的Promise后,最外层的Promise的第二个then相当于挂在内部返回的Promise的最后一个then的返回值上。

3.2 变异版本2

new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{ // p1
    console.log("then11")
    new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{ // p2
        console.log("then21")
    }).then(()=>{ // p3
        console.log("then23")
   })
}).then(()=>{ // p4
    console.log("then12")
})
new Promise((resolve,reject)=>{
    console.log("promise3")
    resolve()
}).then(()=>{ // p5
    console.log("then31")
})

// 输出
// promise1
// promise3
// then11
// promise2
// then31
// then21
// then12
// then23

步骤:
1. 执行主代码
console.log("promise1")  ======================> promise1
console.log("promise3")  ======================> promise3
微任务队列 = [p1 , p5]

2. 清空微任务队列
微任务队列 = [p5]
console.log("then11")  ======================> then11
console.log("promise2")  ======================> promise2
微任务队列 = [p5 , p2]
微任务队列 = [p2 , p4]
console.log("then31")  ======================> then31
微任务队列 = [p4 , p3]
console.log("then21")  ======================> then21
微任务队列 = [p3]
console.log("then12")  ======================> then12
微任务队列 = []
console.log("then23")  ======================> then23

4. 登峰造极版

考点:在async/await之下,对Eventloop的影响。

async function async1() {
    console.log("async1 start");
    await  async2();
    console.log("async1 end"); // async  (await返回一个promise,其后面的部分可以看做是被promise.then包裹)
}

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

console.log("script start");

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

async1();

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () { // p1
    console.log("promise2");
});
console.log('script end');

// 输出
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// settimeout

步骤:
1. 执行主代码
console.log("script start");  ======================> script start
宏任务队列 = [s1]
console.log("async1 start");  ======================> async1 start
console.log( 'async2');  ======================> async2
微任务队列 = [async]
console.log("promise1");  ======================> promise1
微任务队列 = [async , p1]
console.log('script end');  ======================> script end

2. 清空微任务队列
微任务队列 = [p1]
console.log("async1 end");  ======================> async1 end
微任务队列 = []
console.log("promise2");  ======================> promise2

3. 拿出宏任务队列中的第一项 s1
宏任务队列 = []
console.log("settimeout");  ======================> settimeout
该宏任务执行完毕

async/await仅仅影响的是函数内的执行,而不会影响到函数体外的执行顺序。也就是说async1()并不会阻塞后续程序的执行,await async2()相当于一个Promise,console.log("async1 end");相当于前方Promise的then之后执行的函数。

4.1 async/await与promise的优先级详解

async function async1() {
    console.log("async1 start");
    await  async2();
    console.log("async1 end"); // async
}
async  function async2() {
    console.log( 'async2');
}
async1();
// 用于test的promise,看看await究竟在何时执行
new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () { // p1
    console.log("promise2");
}).then(function () { // p2
    console.log("promise3");
}).then(function () { // p3
    console.log("promise4");
}).then(function () { // p4
    console.log("promise5");
});

// 输出
// async1 start
// async2
// promise1
// async1 end
// promise2
// promise3
// promise4
// promise5

步骤:
1. 执行主代码
console.log("async1 start");  ======================> async1 start
console.log( 'async2');  ======================> async2
微任务队列 = [async]
console.log("promise1");  ======================> promise1
微任务队列 = [async , p1]

2. 清空微任务队列
微任务队列 = [p1]
console.log("async1 end");   ======================> async1 end
微任务队列 = []
console.log("promise2");  ======================> promise2
微任务队列 = [p2]
微任务队列 = []
console.log("promise3");  ======================> promise3
微任务队列 = [p3]
console.log("promise4");  ======================> promise4
微任务队列 = [p4]
console.log("promise5");  ======================> promise5

5. 究极变态版

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");
});
async1()
new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
});
setImmediate(()=>{
    console.log("setImmediate")
})
process.nextTick(()=>{
    console.log("process")
})
console.log('script end');

第一轮:

current task:"script start","async1 start",'async2',"promise1",“script end”
micro task queue:[async,promise.then,process]
macro task queue:[setTimeout,setImmediate]

第二轮

current task:process,async1 end ,promise.then
micro task queue:[]
macro task queue:[setTimeout,setImmediate]


第三轮

current task:setTimeout,setImmediate
micro task queue:[]
macro task queue:[]

最终结果:[script start,async1 start,async2,promise1,script end,process,async1 end,promise2,setTimeout,setImmediate]
同样"async1 end","promise2"之间的优先级,因平台而异。

* 干货总结

在处理一段EventLoop的时候

  • 第一步确认宏任务,微任务
    • 宏任务:script,setTimeout,setImmediate,promise中的executor
    • 微任务:promise.then,process.nextTick
  • 第二步解析“拦路虎”,出现async/await的时候不要慌,他们只是在标记的函数中作威作福,出了这个函数还是跟着大部队的潮流。
  • 第三步,根据Promise的then调用方式的不同做出不同的判断,是链式调用还是分别调用。
  • 最后一步,记住一些特别事件:
    • 比如:process.nextTick优先级高于Promise.then

本文是手抄自该文章https://juejin.im/post/5c9a43175188252d876e5903,对题目做了自己的步骤备注,感谢作者!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容