generator的用法 & async-await

generator用法实现以及async-await

什么是generator

在 JavaScript 中,Generator 是一种特殊的函数,它可以产生多个值的序列。Generator 函数使用 function* 声明,并通过 yield 关键字 来定义每个值的生成步骤。

Generator 函数可以被看作是一个状态机,它可以在每次调用 next() 方法时暂停和恢复执行。每次调用 next() 方法时,Generator 函数会执行到下一个 yield 关键字处,并将 yield 后面的值作为结果返回。当再次调用 next() 方法时,Generator 函数会从上次暂停的位置继续执行,直到遇到下一个 yield 关键字或函数结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function* gen() {
let a = yield 1 // js执行是先走等号右边的,遇到yield就停止了
console.log('a',a)
let b = yield 2
console.log('b',b)
let c = yield 3
console.log('c',c)
}

const it = gen()

console.log(it.next()) // 输出: { value: 1, done: false }
console.log(it.next('abc')) // 输出: a abc { value: 2, done: false }
console.log(it.next()) // 输出: { value: 3, done: false }
console.log(it.next()) // 输出: { value: undefined, done: true }

generator执行

通过调用 gen() 创建了一个 Iterator。每次调用 it.next() 方法,Generator 函数会执行到下一个 yield 关键字处,并返回一个包含 value 和 done 属性的对象。value 属性表示生成的值,done 属性表示 Generator 函数是否已经执行完毕

通过连续调用 it.next() 方法,我们可以依次获取生成的值。当 Generator 函数执行完毕后,再次调用 next() 方法将返回 undefined

js执行是先走等号右边的,遇到yield就停止了,如果想传递参数,下一次调用next传递的参数上一个yield的值

需要注意的是,Generator 函数只是一种语法上的特殊函数,它不会立即执行,而是返回一个可迭代的 Generator 对象。我们需要通过调用 next() 方法来逐步获取生成的值。

generator的异步应用

上面例子中我们可以看到 generator函数可以通过 yield 关键字暂停异步操作,并在操作完成后恢复执行。

假如有两个文件文件名分别为name.txt 、 age.txt

1
2
3
4
5
// name.txt
age.txt

// age.txt
18

有了上面的特性我们希望利用generator来这样写我们的异步代码

1
2
3
4
5
6
7
8
9
10

const fs = require('fs/promises') // 这里会把方法promise化
const path = require('path')

function* readResult() { // 依旧是异步,只是看起来像同步的
let filename = yield fs.readFile(path.resolve(__dirname, 'name.txt'), 'utf8')
let age = yield fs.readFile(path.resolve(__dirname, filename), 'utf8')
return age
}

这样写起来有点虽然好,但是调用起来应为是promise,需要我们每次手动去.then (太麻烦了)

1
2
3
4
5
6
7
8
9
let it = readResult()
let { value,done } = it.next()
value.then(data => {
let { value,done } = it.next(data)
value.then(data => {
let { value,done } = it.next(data)
console.log(value) // 输出:18
})
})

终于 TJ 大佬写了个 co库,解决了这个问题,我们只需引入这个 co库 然后 co库 执行传入我们自定义的generator函数即可

1
2
3
4
const co = require('co') // 第三方模块

co(readResult()).then(data => console.log(data)) // 输出:18

这样是不是就简单多了,我们可以想象这个库是如何实现的:

  1. 可以调用.then方法说明返回的是一个promise

  2. co库内部会循环迭代promise

本质来讲 co 库就是帮我实现了上面的循环迭代 promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function co(it){
return new Promise((resolve,reject)=>{ // 同步迭代for循环,异步迭代用回调
function next(data){
const { value,done } = it.next(data)
if(!done){ // 如果没完成返回的一定是一个promise (自己包的)
Promise.resolve(value).then(data => {
next(data)
},reject)
}else{
resolve(value)
}
}
next()
})
}

async + await

上面的读取方法如果换成async + await 会写成这样

1
2
3
4
5
6
7
8
const fs = require('fs/promises') // 这里会把方法promise化
const path = require('path')

async function readResult() { // 依旧是异步,只是看起来像同步的
let filename = await fs.readFile(path.resolve(__dirname, 'name.txt'), 'utf8')
let age = await fs.readFile(path.resolve(__dirname, filename), 'utf8')
return age
}

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

await就是相当于co库的功能,帮我们迭代执行async函数中的方法

async + await 相当于 generator + co库 的语法糖

参考文章:Generator 函数的异步应用async 函数