generator,async,await的原理
Promise 中有很多问题,内部还是使用回调的方式,如果回调过多,还是会带来回调地狱 如何解决这个问题? 我们希望将异步方法写的更像同步一点 今天我们来探讨下 generator,co, async, await简单原理
generator 使用
我们首先基于固定语法实现一个基本的 generator 函数
function* gen() { yield 1 yield 2 } const g = gen() console.log(g.next()) // {value: 1,done: false} console.log(g.next()) // {value: 2,done: false} console.log(g.next()) // {value: undefined,done: true} 复制代码
function*: 这种方式会定义一个生成器函数(generation 函数),返回一个 Generator 对象
yield: 用来暂停或者恢复一个生成器函数,也就是我们多次调用 next 内容通过这个参数分开来
yield* 表达式用于委托给另一个 generator 或可迭代对象。如果有这个标记,另外一个未会一直走
function* tests() { yield 1 yield 2 } function* test() { yield* tests() // 当yieldf* 后面的也是一个生成器函数的时候,方法暂停和回复还会根据嵌套的来 yield 3 } const t = test() console.log(t.next()) // { value: 1, done: false } console.log(t.next()) // { value: 2, done: false } console.log(t.next()) // { value: 3, done: false } console.log(t.next()) // { value: undefined, done: true } 复制代码
既然已经理解的基本使用规则,我们来一起看看 generator 的一些运行原理
generator 运行原理
其实我们可以将 generator 的运行原理,看成 switch + 指针来实现。 下面我们将第一段代码用 js 换种简单方式实现
首先我们知道我们去调用 generator 函数的时候,会返回一个对象,并且这个对象有一个 next 的方法,再次调用 next 的方法时候,会返回一个{value:xx,done:false}的对象
function gen() { return { next() { return { value: '', done: false, } }, } } 复制代码
当然这个 value 和 done 不是一个死的变量,那么我们需要来想办法实现这个参数的变化,从一开始我们了解到了,指针是实现 generator 的方式,那我们先来设定下指针
// 此处通过闭包得方式,来实现对本次gen函数内部变量的一个保存和保护 function gen() { const content { next: 0, // 设置下一步的指针变量 done: flase, // 本迭代是否完成 } ... } 复制代码
现在我们有指针,那我们继续我们伟大的目标,实现当不停得调用 next 的时候,我们会返回不同的参数。
// 我们用一个函数来达到我们的操作 function _gen(content) { // 通过接收到的content中的下一步指针,我们就可以知道我们下一步操作 switch (content.next) { case 0: content.next = 1 return 1 case 1: content.next = 2 return 2 case 2: content.done = true return undefined } } 复制代码
有了操作步骤分解的方法,我们就可以不全我们的 next 方法返回值了
// ... next() { value: _gen(content), done: content.done } // ... 复制代码
到此我们基本完成了简单实现第一段原生 generator 方法实现的效果。 同时我们比对了 babel 转化的核心代码,虽然更加规范,增加了上一步指针,将封装了 done = false 的方法, 但是实际核心还是指针 + switch。
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() } 复制代码
下面我们完善下我们自己写的方法
function gen() { const content = { prev: 0, next: 0, done: false, stop: () => { this.done = true }, } return { next() { return { value: _gen(content), done: content.done, } }, } } function _gen(content) { switch ((content.prev = content.next)) { case 0: content.next = 1 return 1 case 1: content.next = 2 return 2 case 2: content.stop() return undefined } } 复制代码
现在我们对 generator 大概有一个了解了,这时候又有问题来了,我们费力巴切的搞了一堆 generator,是不是偏离主题了~~~
当然不是,下来就让我们进入我们玄妙的 genertor 的世界,来探究他给我们代码可以带来什么
generator + Promise
看到标题我们肯定就知道了,我们要通过 generator + Promise 来实现异步了 但是怎么去实现这个功能呢,按照一般思路,我们可能会写出这样的来吧
const fs = require('fs').promises function* getData() { let path1 = yield fs.readFile('./path.txt', 'utf-8') let name = yield fs.readFile(path1, 'utf-8') return name } const _fs = getData() _fs .next() .value.then((rs) => { console.log(rs) _fs .next(rs) .value.then((rs) => { console.log(rs) }) .catch((error) => { console.log(error) }) }) .catch((error) => { console.log(error) }) 复制代码
但是这个做法好像并没有起到我们今天的目标,反而似的 Promise 变得更加复杂了
co 库
我们继续回想下我们的目标,是不是想要将复杂得 Promise 尽量改成我们想要的如同步一样的写法,我们在此看下上面的 getDate 方法
function* getData() { let path1 = yield fs.readFile('./path.txt', 'utf-8') let name = yield fs.readFile(path1, 'utf-8') return name } 复制代码
是不是很接近我们想要的了,那我们下面应该是,想办法让这个方法能够自动执行,而不是要我们自己去写一堆 Promise 处理方法。 这个 co 库已经给我做了解决方案,我们来简单地看看它的实现的方式。
function co(it) { // 我们最终返回一个Promise return new Promise((resolve, reject) => { // 循环回调,一直到generator最后done为false的时候,不能再迭代的时候 function step(data) { // 迭代一次,获取当前步骤内容 let { value, done } = it.next(data) if (!done) { // 当前不知道具体步骤返回的值是多少,通过Promise.resolve(),得到then的链式调用内容, Promise.resolve(value).then((data) => { step(data) }, reject) } else { // 当所有迭代走完,统一返回出去 resolve(value) } } step() }) } 复制代码
下面我们使用 co()方法来修改读文件例子
function* getData() { let path1 = yield fs.readFile('./path.txt', 'utf-8') let name = yield fs.readFile(path1, 'utf-8') return name } co(getData).then((data) => { console.log(data) //圩上——TAO }) 复制代码
噢噢噢噢哦哦哦哦哦哦~~~~
让我们继续看一眼这一段代码,是不是突然觉得很熟悉,是的,你想的没有错,就是 async + await
async + await
可以这么说 async + await === generator + co
async function getData() { let path1 = await fs.readFile('./path.txt', 'utf-8') let name = await fs.readFile(path1, 'utf-8') return name } getData().then((data) => { console.log(data) }) // 圩上——TAO
作者:圩上
链接:https://juejin.cn/post/7037770990570438663