likes
comments
collection
share

EventLoop 的理解

作者站长头像
站长
· 阅读数 1

一般前端了解eventloop的时候, 最想知道代码执行的先后顺序,并非分析EventLoop。所以这里先说总结。 为什么?因为面试常考这个😂,因为分析和听懂分析都费劲。

##一、总结

  1. js 单线程缺点,容易出现"假死"(如alert()之后,dom不渲染)。优点:保证 dom 的渲染不易出错。
  2. 解决"假死"的问题?其他语言,如java采用多线程去解决,避免占用计算机空间太大,dom 渲染问题易出错问题存在。所以js 采用单线程+异步解决方案。
  3. 单线程 + 异步的实现方式叫做——EventLoop
  4. 加入异步队列方式有两种,第一种是宏任务;第二种是微任务。微任务加入异步队列的优先级宏任务,且是先进先出原则,说得比较绕,简而言之就是,微任务执行完,才会执行宏任务
  5. 微任务:Promise.prototype.then、async await、Process.nextTick(Node独有)、Object.observe(废弃)、MutationObserver
  6. 宏任务:script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。
  7. 执行的优先级即,同步函数(主线程上) -> 异步队列·微任务 -> 异步队列·宏任务

ps: 对纯前端而言,掌握Promise.prototype.then、async await 和 setTimeout 的区别就可以了。另外,Promise、async await是可以转换的,但是浏览器版本问题,async await 的优先级可能高于 Promise(可以忽略这种情况)。

测试题目:

const fn1 = await function () {
	await fn2()
	console.log(1)
}

async function fn2 () {
	await console.log(2)
}

fn1()

setTimeout(function () {
	console.log(3)
})

new Promise(function (resolve, reject) {
	console.log(4)
	resovle()
}).then(function () {
	console.log(5)
}).then(function () {
	console.log(6)
})
// 答案
// 2
// 4
// 1
// 5
// 6
// 3

分析:

  1. 2 和 4 是同步函数
  2. 1、5 和 6 是微任务,异步队列的顺序是1、5、6
  3. 3 是宏任务

代码转换分析

function fn1 () {
	new Promise(function(resolve, reject) {
		console.log(2)
		resolve()
	}).then(function () {
		console.log(1)
	})
}

fn1()

setTimeout(function () {
	console.log(3)
})

new Promise(function (resolve) {
	console.log(4)
	resolve()
}).then(function () {
	console.log(5)
}).then(function () {
	console.log(6)
})

二、内存分析和缘由

阮一峰eventloop www.ruanyifeng.com/blog/2013/1… 看内存分析的这篇 github.com/baiyuze/not…

了解过,可以跳过

三、封装Promise源码分析

了解过,可以跳过

四、uml 类图分析Promise

MDN文档 的 Promise 执行流程图:

EventLoop 的理解

第 1 步 用到的设计模式 和 理解 promise 的简易结构

了解代码第一步,一定要知道他的设计模式,能够节约相当看代码时间。Promise 代码 最主要的模式,观察者模式

EventLoop 的理解

封装一个简易的Promise的 resolvethen,便于理解 Pomise 的封装解构

  • then 从语法结构上讲是然后的意思,但在封装上,是将函数加入异步队列,并返回一个 Promise 的一个类似对象
  • resolve 执行异步队列
class Que {
  _queueLit = []
  constructor (handler) {
    handler.call(this, this._resolve)
  }
  _queue (cb) {
    setTimeout(cb)
  }
  _resolve = () => {
    const { _queueLit, _queue } = this
    const resolve = function () {
      let cb
      while (cb = _queueLit.shift()) {
        cb()
      }
    }
    _queue(resolve)
  }
  then (cb) {
    this._queueLit.push(cb)
    return this
  }
}

// test
setTimeout(() => {
  console.log(4) 
});
new Que(function (resolve) {
  console.log(1)
  resolve()
}).then(function () {
  console.log(2)
}).then(function () {
  console.log(3)
})

// 结果
// 1
// 4
// 2
// 3

后记:不然发现 setTimeout 执行的 callback 为什么是全局的,以及 await 为什么不能在全局环境下,只能在函数内?原因是为了加入异步队列

第 2 步 promise 处理8关键点(第 3 点 最为重要)

上面的封装的代码,简单理解 Promise 封装的解构,现在梳理 Promise的解构,可知道 promise 的根本的两个方法: then 和 _resolve , _queues、_status。reject 、catch等都是在此基础上上进行二次封装。下列数列梳理几个点:

  1. then 接受的函数,是 Promise 时的处理方式
  2. then 接受的函数,是同步函数时的处理方式
  3. then 返回一个 Promise 实例,将该实例要执行的 _resolve 放到上一个 Promise 实例的异步队列中即将执行的异步函数中
  4. _resolve 传递参数的value 是一个Promise 时的处理方式
  5. _resolve 放到 eventloop 中,即 setTimeout(run)中 ,先进先出执行异步队列
  6. _status 状态改变只在 _resolve 中改变
  7. _status 状态判断只在then中执行
  8. 利用自责链之后,每个Promise 的 _queues 只有一个元素, _queues 是里面是多个观察者,这里根据其他人说的,要实现一个 1 对 1 的观察者模式。

第 3 步 从完整版的Promise提取关键代码

源码地址--> coderlt.coding.me/2016/12/04/…

uml 类图

EventLoop 的理解

拿到源码,将源码错误检测、不需要分析的方法通通干掉,避免混淆视听,得到如下代码:

// 判断变量否为function
const isFunction = variable => typeof variable === 'function'

const StatusType = {
  PENDING: 'PENDING',
  FULFILLED: 'FULFILLED',
}

class MyPromise {
  _status = StatusType.PENDING // 添加状态
  _value = undefined // 添加状态
  _fulfilledQueues = [] // 添加成功回调函数队列
  constructor(handle) {
    handle(this._resolve.bind(this))
  }
  _resolve(val) {
    const run = () => {
      if (this._status !== StatusType.PENDING) return
      const runFulfilled = (value) => {
        let cb
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }
      if (val instanceof MyPromise) {
        const resolvePromise = val
        resolvePromise.then(value => {
          this._value = value
          this._status = StatusType.FULFILLED
          runFulfilled(value)
        })
      } else {
        this._value = val
        this._status = StatusType.FULFILLED
        runFulfilled(val)
      }
    }
    setTimeout(run)
  }

  then(onFulfilled) {
    const {
      _value,
      _status
    } = this
    // 返回一个新的Promise对象
    return new MyPromise((onFulfilledNext) => {
      // 封装一个成功时执行的函数
      let fulfilled = value => {
        let res = onFulfilled(value)
        if (res instanceof MyPromise) {
          res.then(onFulfilledNext)
        } else {
          // 下一个 promise 的 resolve 方法的执行
          onFulfilledNext(res)
        }
      }
      switch (_status) {
        case StatusType.PENDING:
          /* 至关重要的代码 */
          this._fulfilledQueues.push(fulfilled)
           /* 至关重要的代码 end */
          break
        case StatusType.FULFILLED:
          fulfilled(_value)
          break
      }
    })
  }
}

五、promise 和 async await

怎么理解,特简单,不用想那么负责。就当 aysnc await 其实就是 promise 的语法糖。

验证:

function a () {
  console.log('a')
  return 'a'
}

async function b () {
  const res = await a('a')
  return res
}

b().then(res => {
  console.log('this is', res)
})

const p = Promise.resolve(b)
console.log('b() is promise', b() instanceof Promise)
console.log('p is promise', p instanceof Promise)
// b() is promise true
// p is promise true
// this is a

总结

分析到这里,基本了解的 promise.then 和 resolve 的实现。reject 相当于 promise 的 resolve 的翻版,catch、all、race 就不在话下,简而言之,promise 源码最终重要的封装是 promise.then 和 resolve.