深入理解Generator、Async、Await的实现

124 阅读3分钟

Javascript异步编程从回调函数、promise、generator、async/await经历多次演化,终于摆脱了回调地狱。相信各位大佬们,日常代码编写使用promise和async组合来编写异步。

这篇文章不是基础知识概念的普及,假如你还对上述API使用和概念不够熟稔,请先阅读回顾学习下阮一峰老师es6,接下来让我们来一探generator和async/await的实现原理。

阅读完文章你将会了解 Javascript中的协程是如何实现的?

首先,我们定义了两个异步操作,这里为了方便,统一用setTimeout来代替,实际代码应用中可能是网络请求、读写文件、操作数据库等等

异步操作

// 异步操作1
function fetch1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(1)
      resolve(1)
    }, 1000)
  })
}
// 异步操作2
function fetch2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(2)
      resolve(2)
    }, 2000)
  })
}

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,async和await在其不久之后出现了,所以日常编写代码大家都会比较少的使用generator来进行异步编程。

generator函数调用后并不会立即执行,而是返回一个遍历器对象iterator。该对象内部维护了一个状态机。通过调用next方法,来执行yield后操作。

function* getUrl() {
   yield fetch1()  
   yield fetch2()     
   yield 3   
   return 4
}
const gen = getUrl();
gen.next()  // 1
gen.next()  // 2
gen.next()  // 3
gen.next()  // 4

Async/Await

async 函数是 Generator 函数的语法糖, 使得我们的generator函数可以自动遍历执行。不用在手动调用next方法

async function getUrl() {
   await fetch1()  
   await fetch2()  
   await 3
   return 4
}
getUrl()

async函数是如何实现的呢?下面是Babel转译后代码实现。注意为了方便理解,我们先把定义的async getUrl转为了generator函数。

// 转为generator函数
function* getUrl_() {
  yield fetch1()  
  yield fetch2()  
  yield 3
  return 4
}
// 递归调用,执行next,理解Promise.resolve是关键
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { 
  try { 
    const info = gen[key](arg)
    const value = info.value
    if (info.done) {      
       resolve(value)
     } else { 
       Promise.resolve(value).then(_next, _throw)
     }   
  } catch (error) { 
     reject(error) 
  } 
}

function asyncToGenerator(fn) { 
  return function () { 
     let self = this
     let args = arguments
     return new Promise(function (resolve, reject) { 
        var gen = fn.apply(self, args)
        function _next(value) { 
           asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
        } 
        function _throw(err) { 
           asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err) 
        } 
        _next(undefined)
     }); 
  }
}
let getUrl= asyncToGenerator(getUrl_)

通过上面的代码,我们理解了async函数语法糖是如何包装generator函数的。那么Generator是如何实现的呢?下面代码是babel转译后的简化

(代码简化版,并不严谨。但是能让我们很好的理解generator实现的本质)

// 存储内部状态
function Context(){
  this.prev = 0
  this.next = 0
  this.args = undefined
  this.done = !1
}

Context.prototype.stop = funtion() {
   this.done = !0
}
const context = new Context()

function Generator() {}
// 包装函数
function wrap(innerFn) {
   Generator.prototype.next = function() {
      const info = innerFn(context)
      return { value: info, done: context.done }
   }
  Generator.prototype.throw = function() {}
  return Generator
}

// 代替上面asyncToGenerator中的getUrl_定义
getUrl_ = wrap(function(_context) {
     while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return fetchR();
          case 2:
            _context.next = 4;
            return fetchY();
          case 4:
            _context.next = 6;
            return 4;
          case 6:
            return _context.abrupt("return", 5);
          case 7:
          case "end":
            return _context.stop();
        }
      }
})

结合这两个部分,我们窥探到了async、await内部机制。同时也理解了generator函数是如何实现暂停、交出执行权的。

generator函数就是javascript对协程的实现。