共计 7281 个字符,预计需要花费 19 分钟才能阅读完成。
原文地址:regenerator
搜了一圈,对于 Generator
根本都是在讲用法,但很少提及到其工作原理,也就是“协程”。但又因为这货色咱们间接或间接的每天都在应用,于是筹备专门写一篇文章来讲讲这个
JS 回调史
一、Callback
- ES5 及更早期间,写回调根本都是 callback,回调天堂就不说了,离它远点
二、Promise
- Promise 通过链式调用,优化了回调的书写形式,实质上也是回调。由其封装进去的
Deferred
也在各大开源库能看到踪影,如 qiankun - Promise 自身没有什么新鲜的货色,但由 then 注册的回调要在以后事件循环的 微工作阶段 去执行这一点,意味着 Promise 只能由原生层面提供。用户层面的 polyfill 只能用宏工作实现,如 promise-polyfill
三、Generator
- Generator 是本文的配角,ES6 重磅推出的个性,能够了解成一个状态机,外面蕴含了各种状态,应用 yield 来触发下一步
- Generator 引入的“协程”概念,是传统回调无法比拟的,这就意味着咱们能够以同步的形式来书写异步代码,再配上主动执行,如 tj 大神的 co 库,几乎美翻
- generator 对象同时实现了:
- 可迭代协定(Symbol.iterator):可通过 for…of 进行迭代,如内置对象 Array、String,它们都实现了这个协定
- 迭代器协定(next()):可调用其 next 办法获取
{value: any, done: boolean}
来判断状态
四、async、await
- Generator、yield 的语法糖,精选了一些个性。反过来说就是舍掉了些性能(后文会讲)
- 用 babel 编译一段含 async、await 和 yield 的代码,可知前者多了两个函数
asyncGeneratorStep
和_asyncToGenerator
,其实它就是主动执行性能 - 原理很简略:
- 获取 Generator 对象,借助 Promise 的微工作能力执行 next
- ret.value 返回的值就是 await 的值,封装成 Promise 当做下次入参
- 判断每次递归后果,直到返回 done 为 true
async function a() {} | |
function* b() {} | |
// babel 编译后 | |
function asyncGeneratorStep(gen, resolve, reject, _next, ...) { | |
// 调用 gen 的 next 或 throw 办法 | |
var info = gen[key](arg); | |
var value = info.value; | |
if (info.done) {resolve(value); | |
} else { | |
// 递归执行 | |
Promise.resolve(value).then(_next, _throw); | |
} | |
} | |
function _asyncToGenerator(fn) {return function () {return new Promise(function (resolve, reject) { | |
// 获取 generator 对象 | |
var gen = fn.apply(self, arguments); | |
function _next(value) {asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); | |
} | |
// 初始化执行 next | |
_next(undefined); | |
}); | |
}; | |
} |
generator Object、Generator、GeneratorFunction
一、generator Object
- 由 Generator 执行后返回,带有 next、return、throw 等原型办法,就是咱们常操作的小伙伴
function* gen() {} | |
const gObj = gen(); | |
gObj.next(); | |
gObj.return(); |
二、Generator
- 可通过
function*
语法来定义,它是 GeneratorFunction 的实例
Object.getPrototypeOf(gen).constructor // GeneratorFunction {prototype: Generator, ...}
- Generator 函数自身在用户代码层面,意义不大,根本不会用到
三、GeneratorFunction
- 它是内置函数,但没有间接挂到 window 上,但咱们能够通过它的实例来获取
const GeneratorFunction = Object.getPrototypeOf(gen).constructor;
- GeneratorFunction 和
Function
是一个级别的,能够传参来创立函数,如
const gen = new GeneratorFunction('a', 'yield a * 2'); | |
const gObj = gen(10); | |
gObj.next().value // 20 |
Generator 的工作原理
正片开始,代码示例:
let num = 0; | |
async function gen() {num = num + (await wait(10)); | |
await 123; | |
await foo(); | |
return num; | |
} | |
function wait(num: number) {return new Promise((resolve) => setTimeout(() => resolve(num), 600)); | |
} | |
async function foo() {await "literal";} | |
await gen(); | |
console.log("regenerator: res", num); |
一、外围点
- Generator 的状态是如何实现的,或者说 Generator 是如何执行到 yield 就进行的
- 多个 Generator 是如何合作的,即如何让权给另一个 Generator,之后又让权回来的
- 一个 Generator 是如何监听另一个 Generator 的执行过程,即 yield* genFn()
二、Generator、GeneratorFunction 及其 prototype 的关系
如果你对原型链和继承有所忘记的话,倡议先看下这篇 prototype&extends
class GeneratorFunction {} | |
// GeneratorFunction 的 prototype 很通用,独自拎进去 | |
class GeneratorFunctionPrototype {static [Symbol.toStringTag] = "GeneratorFunction"; | |
// 实现 iterator protocol | |
next(args) {} | |
return(args) {} | |
throw(args) {} | |
// 实现 iterable protocol | |
[Symbol.iterator]() {return this;} | |
} | |
// 互相援用 | |
GeneratorFunctionPrototype.constructor = GeneratorFunction; | |
GeneratorFunction.prototype = GeneratorFunctionPrototype; | |
// 作用不大,设置 prototype 即可 | |
class Generator {} | |
Generator.prototype = GeneratorFunctionPrototype.prototype; |
二、Generator 的状态
- 状态机实现不难,通过一个 flag 记录状态,每次状态运行后记录下次的状态,肯定机会 后再进入执行
- 状态机是由用户层面代码生成,外面应用
switch case + context 记录参数
实现
function _callee$(_context) {while (1) {switch (_context.next) { | |
case 0: | |
// await wait(10) | |
_context.next = 3; | |
return wait(10); | |
case 3: | |
// await 123 | |
_context.next = 7; | |
return 123; | |
case 7: | |
_context.next = 9; | |
// await foo() | |
return foo(); | |
case "end": | |
return _context.stop();} | |
} | |
} |
- 可知每次 yield 对应着一个 switch case,每次都会 return,天然每次 yield 完后就“卡住了”
三、多个 Generator 合作
- 由 case return 可知 Generator 让权,就是被动执行别的 Generator,并退出本人的状态
- 同理 foo Generator 也是 switch case 这种构造,那它执行完是如何让权回到并触发父级状态机继续执行呢
- 咱们来看 babel 是如何编译 async 函数的。先抛开 mark 和 warp 函数,
_asyncToGenerator
咱们之前说了,就是主动执行,这其实和co(markFn)
无异。另一方面你能够推断出regeneratorRuntime.mark
函数返回的其实就是 polyfill 的 Generator
function _foo() { | |
_foo = _asyncToGenerator(regeneratorRuntime.mark(function _callee2() {return regeneratorRuntime.wrap(function _callee2$(_context2) {switch (_context2.next) { | |
case 0: | |
_context2.next = 2; | |
return "literal"; | |
case "end": | |
return _context2.stop();} | |
}, _callee2); | |
}) | |
); | |
return _foo.apply(this, arguments); | |
} |
- 所以 foo 执行 switch 完,通过解决后把
{value: "literal", done: true}
作为了 mark 函数的返回值,并交给 _asyncToGenerator 应用,它如何应用的呢,当然是promise.then(next)
- 那合作呢?你别只局限于 foo 函数,父级 gen 函数不也是这样!gen 函数这时在干啥,当然是期待 foo resolve,而后 gen 返回
{value: fooRetValue, done: false}
,持续 next - 整顿下:
- ① 父级 gen 函数执行到一个 case,将子 foo 函数的返回值作为本次后果,而后将本人卡住(其实就是在 co 层面期待子 promise resolve)
- ② foo 执行完后返回 done true,并完结本人的状态生涯,再将本人 co 层面的 Promise resolve
- ③ gen 卡住的 Promise 收到了 foo 的后果,本次返回 done false,开启下一轮 next,并从新通过 context.next 进入到对应 case 中
- 所以你能够看出,Generator 来到了 Promise 时成不了大器的,无论是原生实现还是 polyfill,次要起因还是之前说的,咱们没法在 js 层面干预到 v8 的事件循环
四、mark、wrap、Context
- 你应该晓得 mark 函数了:接管一个函数并把它革新成 Generator。怎么做呢,继承啊
function mark(genFn: () => void) {return _inheritsLoose(genFn, GeneratorFunctionPrototype); | |
} | |
function _inheritsLoose(subClass, superClass) {Object.setPrototypeOf(subClass, superClass); | |
subClass.prototype = Object.create(superClass.prototype); | |
subClass.prototype.constructor = subClass; | |
return subClass; | |
} |
- 每个 wrap 会创立一个 context 来治理状态以及上下文参数,每次执行 case 时会先打个 快照,避免 yield 完后参数更改
- mark 函数的 next、return、throw 最终调用是 wrap 的能力,因为理论是 wrap 在协调用户代码(switch case)和 context 来决定接下来的走向,所以要欠缺下 GeneratorFunctionPrototype,让其和 wrap 连接起来,本人只负责传递 type 和 args
type GeneratorMethod = "next" | "return" | "throw"; | |
class GeneratorFunctionPrototype { | |
// set by wrap fn | |
private _invoke: (method: GeneratorMethod, args) => {value: any, done: boolean}; | |
// 留神这是原型办法哦 | |
next(args) {return this._invoke("next", args); | |
} | |
return(args) {return this._invoke("return", args); | |
} | |
throw(args) {return this._invoke("throw", args); | |
} | |
} |
- wrap 实现
function wrap(serviceFn) { | |
// 仍然借用 GeneratorFunctionPrototype 的能力 | |
const generator = new Generator(); | |
const context = new Context(); | |
let state = GenStateSuspendedStart; | |
// 实现 _invoke | |
generator._invoke = function invoke(method: GeneratorMethod, args) { | |
context.method = method; | |
context.args = args; | |
if (method === "next") { | |
// 记录上下文参数 | |
context.sent = args; | |
} else if (method === "throw") {throw args} else {context.abrupt("return", args); | |
} | |
// 执行业务上的代码 | |
const value = serviceFn(context); | |
state = context.done ? GenStateCompleted : GenStateSuspendedYield; | |
return { | |
value, | |
done: context.done | |
}; | |
}; | |
return generator; | |
} |
- Context 记录以后运行状态和上下文参数等,并提供完结、报错、代理等办法
class Context { | |
next: number | string = 0; | |
sent: any = undefined; | |
method: GeneratorMethod = "next"; | |
args: any = undefined; | |
done: boolean = false; | |
value: any = undefined; | |
stop() { | |
this.done = true; | |
return this.value; | |
} | |
abrupt(type: "return" | "throw", args) {if (type === "return") { | |
this.value = args; | |
this.method = "return"; | |
this.next = "end"; | |
} else if (type === "throw") {throw args;} | |
} | |
} |
五、yield* genFn()
最初一点,可能各位用得少,但缺了的话,Generator 是不残缺的
- 之前挖了个坑,await、async 舍弃了的性能就是:一个 Generator 是监听到另一个 Generator 的执行过程。事实上应用 await 咱们并不能晓得子函数经验了多少个 await
async function a() {const res = await b(); | |
} | |
async function b() { | |
await 1; | |
await 'str'; | |
return {data: 'lawler', msg: 'ok'}; | |
} |
- 那在 yield 层面,这个性能是如何实现的呢。实际上 yield* 是通过 delegateYield 办法接替了 foo,在 context 外部循环运行,使得这次 yield 在一个 case 中实现
function gen$(_context) {switch (_context.next) { | |
case 0: | |
_context.next = 7; | |
return wait(10); | |
case 7: | |
// 传递 foo generator object 给 gen 的 context | |
return _context.delegateYield(foo(), "t2", 8); | |
case "end": | |
return _context.stop();} | |
} |
- wrap 外面,循环执行
generator._invoke = function invoke(method, args) { | |
context.method = method; | |
// yield* genFn 时应用,循环返回 genFn 迭代的后果,直到 return | |
while (true) { | |
const delegate = context.delegate; | |
if (delegate) {const delegateResult = maybeInvokeDelegate(delegate, context); | |
if (delegateResult) {if (delegateResult === empty) continue; | |
// 传出外部迭代后果 {value, done} | |
return delegateResult; | |
} | |
} | |
} | |
if (method === "next") {}} |
最初
- 本文只是简略对 Generator 进行了实现,实际上
regenerator
做的事件还很多,如 throw error、yield* gen() 时各种情况的解决以及其余不便的 api,喜爱的自行 dive in 吧 - 通过本文对 Generator 工作原理的解说,让咱们对“协程”这个概念更加粗浅的意识,这对于咱们每天都要用的货色、调试的代码都有“磨刀不误砍柴工”的效用
- 源码获取:regenerator
- 码字不易,喜爱的小伙伴,记得留下你的小 ❤️ 哦~
参考资料
- MDN Generator
- MDN Iteration protocols
- regenerator
- co
正文完
发表至: javascript
2021-04-08