regenerator的成果

regenerator是一个es5环境下实现es6的generator的形式,

regenerator 分为编译时和运行时两块,编译工具会负责将generator编译成es5,比方:

这段代码:

const len = 100;function *gen() {  console.log('gen !');  for (let i = 0; i < len; i++) {    yield i;  }  return 'what';}const g = gen();for (let i = 0; i < len; i++) {  console.log(g.next());}

会被编译成

'use strict';var _marked =/*#__PURE__*/regeneratorRuntime.mark(gen);var len = 10;function gen() {  var i;  return regeneratorRuntime.wrap(function gen$(_context) {    while (1) {      switch (_context.prev = _context.next) {        case 0:          console.log('gen !');          i = 0;        case 2:          if (!(i < len)) {            _context.next = 7;            break;          }          _context.next = 5;          return i++;        case 5:          _context.next = 2;          break;        case 7:          return _context.abrupt("return", 'what');        case 8:        case "end":          return _context.stop();      }    }  }, _marked);}var g = gen();for (var i = 0; i < len; i++) {  console.log(g.next()); }

其中的regeneratorRuntime就是regenerator的运行时局部。

编译后产生的 switch 代码能够了解为定义的各个状态,以及状态间转化的逻辑

运行时

运行时咱们次要看一下regeneratorRuntime这个对象提供的内容。regeneratorRuntime的源代码地址

regeneratorRumtime在下面编译后的代码里,提供了两个办法,wrapmark

wrap

wrap办法会返回一个generator对象,也就是合乎es6规范的迭代器,蕴含办法:

  • next
  • throw
  • return

依照迭代器的定义,调用next办法会是迭代器从上次yield的地位前面继续执行。每调用一次next办法,都会调用一次编译后的gen$ 办法。$gen 办法内会批改context上下文中的next值,达到一个驱动迭代器的成果。

gen$ 这个函数很像是一个reducer
// line 274if (context.method === "next") {          // Setting context._sent for legacy support of Babel's          // function.sent implementation.          context.sent = context._sent = context.arg;} //...// line 293var record = tryCatch(innerFn, self, context);if (record.type === "normal") {  // If an exception is thrown from innerFn, we leave state ===  // GenStateExecuting and loop back for another invocation.  state = context.done    ? GenStateCompleted    : GenStateSuspendedYield;  if (record.arg === ContinueSentinel) {    continue;  }  return {    value: record.arg,    done: context.done  };} // ...

这里的innerFn就是之前的 gen$ 函数,tryCatch(innerFn, self, context) 就会调用 gen$ 函数,gen$ 会返回一个后果,如果没有异样,就会返回next 办法的规范返回 { value: any, done: boolean }

tryCatch 办法 会捕捉 gen$ 中抛出的异样。如果有捕捉到,则会返回异样的状态

// line 61function tryCatch(fn, obj, arg) {  try {    return { type: "normal", arg: fn.call(obj, arg) };  } catch (err) {    return { type: "throw", arg: err };  }}

mark

mark办法会将gen 迭代器函数的原型指向runtime外部定义的generator对象

那么regenerator外部是怎么定义generator对象的呢?

// line 82  function Generator() {}  function GeneratorFunction() {}  function GeneratorFunctionPrototype() {}  // This is a polyfill for %IteratorPrototype% for environments that  // don't natively support it.  var IteratorPrototype = {};  IteratorPrototype[iteratorSymbol] = function () {    return this;  };  var getProto = Object.getPrototypeOf;  var NativeIteratorPrototype = getProto && getProto(getProto(values([])));  if (NativeIteratorPrototype &&      NativeIteratorPrototype !== Op &&      hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {    // This environment has a native %IteratorPrototype%; use it instead    // of the polyfill.    IteratorPrototype = NativeIteratorPrototype;  }  var Gp = GeneratorFunctionPrototype.prototype =    Generator.prototype = Object.create(IteratorPrototype);  GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;  GeneratorFunctionPrototype.constructor = GeneratorFunction;  GeneratorFunction.displayName = define(    GeneratorFunctionPrototype,    toStringTagSymbol,    "GeneratorFunction"  );// line 111// line 115 function defineIteratorMethods(prototype) {    ["next", "throw", "return"].forEach(function(method) {      define(prototype, method, function(arg) {        return this._invoke(method, arg);      });    });  }// line 406  defineIteratorMethods(Gp);

line 82 - line 111 定义了几个dummy constructor,并且定义了它们之间原型链关系,这些关系的设置是依照 ES6 标准 来设置的。

line 115 defineIteratorMethods 申明了一个定义generator外部办法的函数,函数中 define 办法外部理论是 Object.define 办法的封装。

line 406 应用 defineIteratorMethods,传入Gp,使得Gp成为一个generator对象。defineIteratorMethods定义了next, throw, return 三个办法

context

generator的上下文,即状态机的上下文,提供了多个办法和变量,《runtime.js#L521》

办法:

  • reset
  • stop
  • dispatchException
  • abrupt
  • complete
  • finish
  • catch

变量:这些变量的初始化都在reset办法里,reset办法会在构造函数外面被调用

  • next number - 状态机的下一个状态值
  • pre number - 状态机的上一个状态值
  • sent
  • done
  • delegate
  • method
  • arg

编译时

编译时,迭代器里的内容会被编译成switch...case模式的代码,其中各个case根本是以yield关键词作为划分case的根据,而后整个switch...case会被包裹在一个while(1)的循环里,只有当某一个case应用return返回时才会跳出while循环。

比方一个简略的,可迭代2次的迭代器

'编译前'function *gen() {  yield 1;  yield 2;}

其中的2次yield,在编译后果里,就会被拆成2个case, case 0 和 case 2

'编译后'function gen() {  return regeneratorRuntime.wrap(function gen$(_context) {    while (1) {      switch (_context.prev = _context.next) {        case 0:          _context.next = 2;          return 1;        case 2:          _context.next = 4;          return 2;        case 4:        case "end":          return _context.stop();      }    }  }, _marked);}

那如果迭代器外面有for循环呢,是怎么解决的?

'编译前'function *gen() {  for (let i = 0; i < len; i++) {     before = 'yield前语句'     yield i;     after = 'yield后语句'  }}

for循环中,每一次循环会被拆成多个case

function gen() {  var i;  return regeneratorRuntime.wrap(function gen$(_context) {    while (1) {      switch (_context.prev = _context.next) {        case 0:          i = 0;        case 1:          if (!(i < len)) {            _context.next = 9;            break;          }          before = 'yield前语句';          _context.next = 5;          return i;        case 5:          after = 'yield后语句';        case 6:          i++;          _context.next = 1;          break;        case 9:        case "end":          return _context.stop();      }    }  }, _marked);}


for 循环会被拆成几局部,首先for(...)括号里的三局部会被拆成三个局部

  • initialization - 初始化语句 对应 case 0
  • condition - 条件 对应 case 1 中结尾局部
  • final expression - 最终表达式 对应 case 6

for循环外部逻辑:

  • case 1 和 case 5 别离示意for的大括号外面,yield前和yield后代码。

参考:

ES6 系列之 Babel 将 Generator 编译成了什么样子

从协程到状态机--regenerator源码解析(一)