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

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

相关推荐
哀木1 小时前
一个简单的套壳方案,就能让你的 Agent 少做重复初始化
前端
问心无愧05131 小时前
ctf show web入门27
前端
小村儿1 小时前
给 AI Agent 装上"长期记忆":Karpathy 的 LLM Wiki 思想,我做成了工具
前端·后端·ai编程
竹林8181 小时前
用ethers.js连接MetaMask实现Web3钱包登录:从踩坑到稳定运行的完整记录
前端·javascript
heyCHEEMS1 小时前
如何用 Recast 实现静态配置文件源码级读写
前端·node.js
心连欣1 小时前
从零开始,学习所有指令!
前端·javascript·vue.js
review445432 小时前
大模型和function calling分别是如何工作的
前端
东东同学2 小时前
耗时一个月,我把 Nuxt 首屏性能排障经验做成了一个 AI Skill
前端·agent
冴羽3 小时前
超越 Vibe Coding —— AI 辅助编程指南
前端·ai编程·vibecoding
梦想的颜色3 小时前
一天一个SKILL——前端最佳自动化测试 webapp-testing
前端·web app