generator 基本语法和特点
*
、 yeild
function* read() {
yield 1
yield 2
yield 3
yield 4
return 5
}
我们运行下面代码
let it = read()
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())
得到结果,利用 next 进行迭代,特点就是可以暂停,只要遇到一个 yield 就暂停一次,一开始read 的时候是遇到第一个 yield 被暂停了, 可以迭代到 done 为 true
generator 的应用: 生成迭代器
如果我们想把一个普通的对象转换成一个数组,我们尝试用 Array.from(obj)
let obj = {
0: 1,
1: 2,
length: 2
}
console.log(Array.from(obj))
发现可行
再尝试用
[...obj]
let obj = {
0: 1,
1: 2,
length: 2
}
console.log([...obj])
会发现报错,提示 obj 不是可迭代的
那么我们硬是要用
[...obj]
来将对象转成数组呢,我们就需要对对象进行改造,使其编程可迭代的。我们需要提供一个迭代的接口,迭代器是一个对象,并且有一个 next 方法,next 方法每次返回{value:x,done:true/false}
首先,我们自己模拟一个迭代器
let obj = {
0: 1,
1: 2,
[Symbol.iterator]() { //可迭代的方法
let index = 0
return {
next: () => { //如果不用箭头函数,函数内部的 this 不是pbj
return {
value: this[index], //这个this是构造对象前的this,对象被构造完才会有自己的this
done: this.length === index++
}
}
}
}, //元编程,修改js 的行为
length: 2
}
console.log([...obj])
接下来我们利用 gnerator 的特点,利用其产生迭代器
let obj = {
0: 1,
1: 2,
*[Symbol.iterator]() { //可迭代的方法
for (let i = 0; i < this.length; i++) {
yield this[i]
}
}, //元编程,修改js 的行为
length: 2
}
console.log([...obj])
generator 与 Promise 结合使用
首先来看这样一段代码
function* read() {
let a = yield 'hello'
console.log(a)
let b = yield 'world'
console.log(b)
}
let it = read()
console.log(it.next())
console.log(it.next())
console.log(it.next())
其结果是
说明 a 和 b 都没有被赋值,我的理解是这样的,可以理解为下面这段代码(为个人揣测, 未考证过,错误请指正!)
function* read() {
let a
//赋值被暂停
yield 'hello'
console.log(a)
let b
//赋值被暂停
yield 'world'
console.log(b)
//结束
}
那么我们如何对 a 和 b 进行赋值嗯?可以在 next 中传值,传递给作为上一次 yield 的返回值
let it = read()
console.log(it.next()) //第一次 next 传参是无意义的
console.log(it.next(1)) //会传递给上一次 yield 的返回值
console.log(it.next(2))
如果我们现在有一个需求,就是读取一个文件,这个文件中有下一个需要读取的文件的地址,我们创建一个 read 迭代器
let fs = require('fs').promises //读取完的结果是 Promsie
function* read() {
let content = yield fs.readFile('./name.txt', 'utf8')
let r = yield fs.readFile(content, 'utf8')
return r
}
显然,我们可以通过 next 来传递上一次 yield 表达式读到的地址给等号左边进行赋值(实现了异步赋值),并给下一次读取提供地址,有一点绕。
let it = read()
let {
value,
done
} = it.next()
//保证是 Promise
Promise.resolve(value).then(data => {
let {
value,
done
} = it.next(data)
Promise.resolve(value).then(data => {
let {
value,
done
} = it.next(data)
console.log(value)
})
})
可以看到这其实是一种嵌套,这种写法比较丑,我们应该把它改成递归的形式,有一个第三方库 co,可以看一看。我们自己来简单实现一下自己的 co 函数。
co 简单实现
我们希望实现的效果是,执行一次就能得到嵌套读取的最后结果
co(read()).then(data => {
console.log(data)
}, err => {
console.log(err)
})
co 简单实现:
function co(it) {
return new Promise((resolve, reject) => {
function next(data) {
let {
value,
done
} = it.next(data)
//按照 done 状态进行递归,直到为 true
if (!done) {
Promise.resolve(value).then(data => {
next(data) //给上一个 yield 返回值
}, reject)
} else {
resolve(data)
}
}
next() //第一次不需要传
})
}
async & await
要实现 generator + co 的效果,我们只需要用 async 和 await 这个语法糖即可,它会让异步代码看起来像是同步执行的。
async function read() {
try {
let content = await fs.readFile('./name.txt', 'utf8')
let r = await fs.readFile(content, 'utf8')
return r
} catch (e) {
console.log(e)
}
}