生成器函数 模拟 async / await 思路

前言

首先对这个语法好奇是因为我们常用的 async/await 就是基于生成器函数 创建的。async/await 就是生成器函数语法糖

是实际上使用到它的场景非常少,我第一次见到,还是在很久以前 unity3D 语法里有见到过。但当时的我还不知道啥是 js.....在大部分 Web 端开发中也非常少见,自己更是忘了记,记了忘...

那么在了解下其基础内容。

生成器函数 Generator

生成器函数执行时能暂停 ,后面又能从暂停处继续执行 。(如果对 React Fiber 有了解的应该会马上想到 Fiber 也是这个作用,实际上之前也有人在 issue 里问过 为什么不使用 生成器函数 来做 fiber,官方给了 2 个原因,这里就不具体说明了,有兴趣的小伙伴可以搜下)

ts 复制代码
// 定义一个生成器函数
function* generator(i) {
  yield i
  yield i + 10
}

// 调用生成器函数,返回该生成器的迭代器对象
const gen = generator(10)

console.log(gen.next().value) // 10

console.log(gen.next().value) // 20

// gen.next() 会返回 { value , done } , done 表示是否结束

这个是 MDN 上的例子,简要概括了生成器函数 的语法,重点在于 *yield。首先第一步我们是用 function * 创建了一个生成器函数 ,且在后面我们返回了一个名为 gen迭代器对象

当我们首次 调用迭代器的 next() 方法时,生成器函数 内的语句会执行到第一个 yield 语句的位置,并返回 yileld 后面的值console.log(gen.next().value) // 10 这里就对应了我们第一次调用,且返回值为 10 ,调用 next().value 可以获取到返回值。

next 中传递参数

调用 next() 方法时,是允许给其传入参数的,且这个参数会传给上一条执行的 yield 语句左边的变量。听着可能会觉得有点拗口,用 MDN 里的例子来说明就是:

ts 复制代码
function* gen() {
  yield 10
  const x = yield 'foo'
  yield x
}

var gen_obj = gen()
console.log(gen_obj.next()) // 执行 yield 10,返回 10
console.log(gen_obj.next()) // 执行 yield 'foo',返回 'foo'

// 这里如果什么都不传递,会打印 undefined ,原因看下面的 【注】
console.log(gen_obj.next(100)) // 将 100 赋给上一条 yield 'foo' 的左值,即执行 x=100,返回 100
console.log(gen_obj.next()) // 执行完毕,value 为 undefined,done 为 true

// 正常情况下,我们 gen_obj.next(100) 时是执行到第三条 yield 语句,即 yield x
// 由于我们传递了参数,所以这时候,会把这个 参数 赋值给上一条 yield 语句的左值。即 const x = 100
// 所以,最后 yield x 会返回 100

// 【注】如果你不传递参数,那么相当于将 undefined 赋值给了 x,const x = undefined
// 最终会打印 undefined 的

yield*

如果 yield 后面跟着的是 *,则表示将执行权移交给另一个生成器函数,当前生成器暂停执行。

同样用 MDN 上的例子来说明,很清晰明了,点个赞~

ts 复制代码
function* anotherGenerator(i) {
  yield i + 1
  yield i + 2
  yield i + 3
}

function* generator(i) {
  yield i
  yield* anotherGenerator(i) // 移交执行权
  yield i + 10
}

var gen = generator(10)

console.log(gen.next().value) // 10
console.log(gen.next().value) // 11
console.log(gen.next().value) // 12
console.log(gen.next().value) // 13
console.log(gen.next().value) // 20

async/await

介绍完了生成器函数 的基础,那么它是如何实现 async/await 的功能的呢?

首先我们需要明确一点: async/await 能实现的原因在于,用户可以主动触发 gen.next()

js 复制代码
const p1 = () => {
  return new Promise((resolve) => {
    console.log('1')
    setTimeout(() => {
      resolve(2)
    }, 1000)
  })
}

我们先创建一个 promise 对象,内容很简单就是先打印 1 ,然后再 1秒之后打印 2,来,我们思索下,再结合上面的知识。

思路:

如果把 p1() 的结果作为 yield右值 ,那么当用户调用 gen.next() 时,就会在 p1() 这里停住。 然后我们的 p1() 因为是一个 promise ,所以它有 .then() 方法,那么只需要在 .then() 里再调用一次 gen.next() 就可以实现 async/await 的功能

简化版代码:
js 复制代码
const p1 = () => {
  return new Promise((resolve) => {
    console.log('1')
    setTimeout(() => {
      resolve(2)
    }, 1000)
  })
}

function* autoAwait(p1) {
  const x = yield p1()
  console.log(x)
}

// 生成生成器的迭代器对象
const a1 = autoAwait(p1)
a1.next().value.then((res) => {
  a1.next(res)
})

可能大家伙会好奇,哎呀,长得也不一样, async/await 才没辣么多代码。但其实你会发现,当我们把 autoAwait 转变一下。

js 复制代码
async function autoAwait(p1) {
  const x = await p1()
  console.log(x)
}

他就变成了我们的 async/await 了,实际上当你使用 async/await 时,他内部就是做了类似的操作,只不过没有将这些暴露给用户。

当然我的代码也不够完整,没有任何其他的机制,只是介绍了下其思路,感兴趣的话还是建议大家去了解源码。

相关推荐
嘻嘻哈哈1710 分钟前
vue安装+测试
前端·javascript·vue.js
大学生小郑17 分钟前
VUE与React的生命周期对比
前端·vue.js·react.js
JerryXZR22 分钟前
Angular基础保姆级教程 - 1
前端·javascript·angular.js
金灰23 分钟前
14-Django项目--文件上传-Excel
服务器·前端·javascript
inksci28 分钟前
用 Echarts 画折线图
前端·javascript·html·echarts
JUANのWEB34 分钟前
【WEB前端】---HTML---结构---笔记
前端·笔记·html
夏花里的尘埃3 小时前
vue3实现echarts——小demo
前端·vue.js·echarts
努力学习的木子4 小时前
uniapp如何隐藏默认的页面头部导航栏,uniapp开发小程序如何隐藏默认的页面头部导航栏
前端·小程序·uni-app
java小郭7 小时前
html的浮动作用详解
前端·html
水星记_7 小时前
echarts-wordcloud:打造个性化词云库
前端·vue