阅读 314

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


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐