Event Loop 的异步流程

内容参考

执行队列

microtask

当前JS执行loop的尾部

macrotask

下个JS执行loop的首部

JS的执行顺序

JS逐行执行 (首先执行同步代码)
1【1行,2行,3行】
pop()出栈
2【microtask】
(eventLoop)进入下个loop
3【macrotask】

引用
MicroTask和macroTask的api范畴
1macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
2microtasks: process.nextTick, Promises, Object.observe, MutationObserver
stackoverflow

Promise 的异步执行

const p = new Promise((res, rej) => {
  res(1)
  console.log('定义new Promise - 同步')
}).then(val => {
  console.log('microtask start')
  console.log('执行then,enqueue micarotask 1')
  console.log(val) // 1
})

Promise.resolve({
  then(res, rej) {
    console.log('执行then,enqueue micarotask 2')
    res(5)
  }
}).then(val => {
  console.log('执行then,enqueue micarotask 3')
  console.log(val) // 5
})

console.log('逐行执行1 - 同步')
console.log('逐行执行2 - 同步')
console.log(3) // 3

setTimeout(console.log, 0, 'macrotask start') // 4 
setTimeout(console.log, 0, 4) // 4 
// 执行结果如下
定义new Promise - 同步
逐行执行1 - 同步
逐行执行2 - 同步
3
// 同步队列执行完毕为空 进入下一个栈

microtask start
执行then,enqueue micarotask 1
1
执行then,enqueue micarotask 2
执行then,enqueue micarotask 3
5
// microtask执行完毕为空 进入下一个栈

macrotask start
4
9
// macrotask执行完毕为空 结束

结论:Promise的then API把语句压人micarotask内执行,setTimout则压macrotask
总结:JS先执行同步代码,再执行micarotask,最后执行macrotask

generator 异步执行

generator 基础API

遍历逻辑

const generator_souce = function* () {
  console.log('start            遇到yield暂停')
  const a = yield 1
  console.log('a === ' + a + '  遇到yield暂停')
  const b = yield 2
  console.log('b === ' + b + '  没有yield执行所有语句')
  console.log('end')
}

const g = generator_souce()
for(let i of g) {
  console.log(i)
} // for of 循环遍历iteration
/* 执行结果
start            遇到yield暂停
1 // 确定const a = yield 1的结果为1
a === undefined  遇到yield暂停
2 // 确定const b = yield 2的结果为2
b === undefined  没有yield执行所有语句
end
*/

传值逻辑

当我们换成next() API执行generator函数时,generator函数内yield赋值的变量不能在函数体内进行传递

g.next()
g.next()
g.next()
/*  
start            遇到yield暂停
a === undefined  遇到yield暂停
b === undefined  没有yield执行所有语句
end
*/

next()执行后返回的value为generator函数内yield产出的值
generator函数内yield赋值的变量只能靠在next().value进行传递

const c = g.next()
const d = g.next(c.value)
g.next(d.value)
/*
start    遇到yield暂停
a === 1  遇到yield暂停
b === 2  没有yield执行所有语句
end
*/

co模块

co模块其实就是封装了传值逻辑,内部实现使用了Promise

面向过程的co
const g = generator_souce()

const co = generator => {
  const a = generator.next()
  if(!a.done) {
    const b = generator.next(a.value)
    if(!b.done) {
      const c = generator.next(b.value)
      if(!c.done) {
        const d = genterator.next(c.value)
      }
    }
  }
}
co(g)

使用Promise实现的简单co co-light


const g = generator_souce()

const co = (gen)=>{
    let basePromise=(value)=>{
            let nextGen = gen.next(value);              
            let nextVal = nextGen['value'];
            if(!nextGen.done){
                return Promise.resolve(nextVal).then(basePromise)
            }else{
                return Promise.resolve(nextVal)
            }
        };
    return new Promise((resolve,reject)=>{
        //启动generator
         let start = gen.next()['value'];
         Promise.resolve(start).then(resolve)
    }).then(basePromise).catch((error)=>{
        gen.throw('generator throw ------' + error.name + error.stack)  
    });
}
co(g)
执行结果
/*
start            遇到yield暂停
a === 1  遇到yield暂停
b === 2  没有yield执行所有语句
end
*/

aync - await

await后面必须接一个Promise,返回的值就是这个Promise最后返回的值

const fn = async (val) => {
    const await1 = await 1
}
// 等价于
const fn = async (val) => {
    const await1 = await Promise.resolve(1)
}
const fn = async (val) => {
  const await1 = await new Promise((resolve, reject) => {
    setTimeout(resolve, val*500, val)
  }).then(val => {
    return val + 1
  })
  console.log(`await函数返回的value === ${await1}`)
}

fn(2)
fn(3)
执行结果
/*
await函数返回的value === 3
await函数返回的value === 4
*/

简单理解就是await后面的所有Promise完成之后变量才被赋值

简单原理:
aync + await 降级=> generator + 自动执行 转化=> Promise + 语法转换

setTimeout (microtask 和 macrotask)

1macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
2microtasks: process.nextTick, Promises, Object.observe, MutationObserver
同步代码执行完毕立即执行microtasks,效率更高

process.nextTick(() => console.log('tick0'))
setTimeout(console.log, 0, '0s')
process.nextTick(() => console.log('tick1'))
setTimeout(console.log, 1000, '1s')
setTimeout(console.log, 2000, '2s')
console.log('1')
console.log('2')
/* 执行顺序 [同步代码] pop() [microtasks] eventLoop() [macrotasks]
1
2
tick0
tick1
0s
1s
2s
*/

MutationObserver

vue2.0使用MutationObserver API作为异步更新队列的DOM更新解决方案
MutationObserver属于microtasks,执行效率更高,优先于setTimeout执行

    // Firefox和Chrome早期版本中带有前缀
    const MutationObserver = window.MutationObserver
    || window.WebKitMutationObserver || window.MozMutationObserver
    // 创建观察者对象
    const observer = new MutationObserver(mutations=>{
      mutations.forEach(function(mutation) {
        console.log(mutation.type);
      })   
    }) 
    // 配置观察选项:
    const config = { 
      attributes: true, 
      childList: true, 
      characterData: true 
    }
    // 选择目标节点
    const target = document.querySelector('#test');
    // 传入目标节点和观察选项
    observer.observe(target, config);

    target.appendChild(document.createElement("div"))   
    /*
    * mutationObserver优先于setTimeout
    */
    setTimeout(console.log,0,'setTimeout')
    console.log('appending')  
    target.setAttribute('class','hello')                       //添加了一个元素子节点,触发回调函数.

    // 随后,你还可以停止观察
    // observer.disconnect();


    /*
    * doc  https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
    */
执行结果
/*
appending
childList
attributes
setTimeout
*/

MutationObserver

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

推荐阅读更多精彩内容

  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,707评论 0 5
  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,301评论 5 22
  • 由于JavaScript是单线程的语言,因此异步编程对于js的重要程度可想而知,可以说没有异步的js程序寸步难行。...
    会飞小超人阅读 954评论 0 7
  • 本文首发在个人博客:http://muyunyun.cn/posts/7b9fdc87/ 提到 Node.js, ...
    牧云云阅读 1,679评论 0 3
  • 本文旨在通过实例演示JS异步编程几种常见的解决方案,让你写出最优雅的异步代码。 异步是Javascript区别于其...
    沐童Hankle阅读 3,358评论 2 3