前言
首先对这个语法好奇是因为我们常用的 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
时,他内部就是做了类似的操作,只不过没有将这些暴露给用户。
当然我的代码也不够完整,没有任何其他的机制,只是介绍了下其思路,感兴趣的话还是建议大家去了解源码。