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

你可能感兴趣的:(Event Loop 的异步流程)