生成器函数 模拟 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 时,他内部就是做了类似的操作,只不过没有将这些暴露给用户。

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

相关推荐
Ikalus19886 小时前
三套 API 都告诉我成功了,浏览器一个字都没收到
javascript
开开心心就好6 小时前
办公软件卸载不干净?专用工具一键清残留
java·前端·人工智能·智能手机·kafka·excel·memcache
Ikalus19886 小时前
计数器停在 0,我差点把一篇空摘要发出去
javascript
静默回滚6 小时前
图片能显示,画进canvas却空白:像素面积上限
前端
补码缩写6 小时前
canvas大图内存怎么算:宽×高×4只是第一份
前端
coding漫漫长路6 小时前
img.decode()报错的图能画,load成功的却是空的
前端
不可能片场6 小时前
AI视频首尾帧控制 用参考图定住镜头
前端·electron
修炼的dance6 小时前
canvas最大能开多大:先看面积,再看导出格式
前端
付威20236 小时前
Rust 所有权:看你的答对几道题?
javascript
西红柿炖牛腩3546 小时前
Three.js 模型体积优化:Draco/Meshopt 压缩与 DRACOLoader 配置的 4 个步骤
javascript·3d·性能优化