Promise系列文章
Promise 中有很多问题,内部还是使用回调的方式,如果回调过多,还是会带来回调地狱 如何解决这个问题? 我们希望将异步方法写的更像同步一点 今天我们来探讨下 generator,co, async, await简单原理
generator 使用
我们首先基于固定语法实现一个基本的 generator 函数
function* gen() {
yield 1
yield 2
}
const g = gen()
console.log(g.next()) // {value: 1,done: false}
console.log(g.next()) // {value: 2,done: false}
console.log(g.next()) // {value: undefined,done: true}
- function*: 这种方式会定义一个生成器函数(generation 函数),返回一个 Generator 对象
- yield: 用来暂停或者恢复一个生成器函数,也就是我们多次调用 next 内容通过这个参数分开来
- yield* 表达式用于委托给另一个 generator 或可迭代对象。如果有这个标记,另外一个未会一直走
function* tests() {
yield 1
yield 2
}
function* test() {
yield* tests() // 当yieldf* 后面的也是一个生成器函数的时候,方法暂停和回复还会根据嵌套的来
yield 3
}
const t = test()
console.log(t.next()) // { value: 1, done: false }
console.log(t.next()) // { value: 2, done: false }
console.log(t.next()) // { value: 3, done: false }
console.log(t.next()) // { value: undefined, done: true }
既然已经理解的基本使用规则,我们来一起看看 generator 的一些运行原理
generator 运行原理
其实我们可以将 generator 的运行原理,看成 switch + 指针来实现。 下面我们将第一段代码用 js 换种简单方式实现
- 首先我们知道我们去调用 generator 函数的时候,会返回一个对象,并且这个对象有一个 next 的方法,再次调用 next 的方法时候,会返回一个{value:xx,done:false}的对象
function gen() {
return {
next() {
return {
value: '',
done: false,
}
},
}
}
- 当然这个 value 和 done 不是一个死的变量,那么我们需要来想办法实现这个参数的变化,从一开始我们了解到了,指针是实现 generator 的方式,那我们先来设定下指针
// 此处通过闭包得方式,来实现对本次gen函数内部变量的一个保存和保护
function gen() {
const content {
next: 0, // 设置下一步的指针变量
done: flase, // 本迭代是否完成
}
...
}
- 现在我们有指针,那我们继续我们伟大的目标,实现当不停得调用 next 的时候,我们会返回不同的参数。
// 我们用一个函数来达到我们的操作
function _gen(content) {
// 通过接收到的content中的下一步指针,我们就可以知道我们下一步操作
switch (content.next) {
case 0:
content.next = 1
return 1
case 1:
content.next = 2
return 2
case 2:
content.done = true
return undefined
}
}
- 有了操作步骤分解的方法,我们就可以不全我们的 next 方法返回值了
// ...
next() {
value: _gen(content),
done: content.done
}
// ...
到此我们基本完成了简单实现第一段原生 generator 方法实现的效果。 同时我们比对了 babel 转化的核心代码,虽然更加规范,增加了上一步指针,将封装了 done = false 的方法, 但是实际核心还是指针 + switch。
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2
return 1
case 2:
_context.next = 4
return 2
case 4:
case 'end':
return _context.stop()
}
下面我们完善下我们自己写的方法
function gen() {
const content = {
prev: 0,
next: 0,
done: false,
stop: () => {
this.done = true
},
}
return {
next() {
return {
value: _gen(content),
done: content.done,
}
},
}
}
function _gen(content) {
switch ((content.prev = content.next)) {
case 0:
content.next = 1
return 1
case 1:
content.next = 2
return 2
case 2:
content.stop()
return undefined
}
}
现在我们对 generator 大概有一个了解了,这时候又有问题来了,我们费力巴切的搞了一堆 generator,是不是偏离主题了~~~
当然不是,下来就让我们进入我们玄妙的 genertor 的世界,来探究他给我们代码可以带来什么
generator + Promise
看到标题我们肯定就知道了,我们要通过 generator + Promise 来实现异步了 但是怎么去实现这个功能呢,按照一般思路,我们可能会写出这样的来吧
const fs = require('fs').promises
function* getData() {
let path1 = yield fs.readFile('./path.txt', 'utf-8')
let name = yield fs.readFile(path1, 'utf-8')
return name
}
const _fs = getData()
_fs
.next()
.value.then((rs) => {
console.log(rs)
_fs
.next(rs)
.value.then((rs) => {
console.log(rs)
})
.catch((error) => {
console.log(error)
})
})
.catch((error) => {
console.log(error)
})
但是这个做法好像并没有起到我们今天的目标,反而似的 Promise 变得更加复杂了
co 库
我们继续回想下我们的目标,是不是想要将复杂得 Promise 尽量改成我们想要的如同步一样的写法,我们在此看下上面的 getDate 方法
function* getData() {
let path1 = yield fs.readFile('./path.txt', 'utf-8')
let name = yield fs.readFile(path1, 'utf-8')
return name
}
是不是很接近我们想要的了,那我们下面应该是,想办法让这个方法能够自动执行,而不是要我们自己去写一堆 Promise 处理方法。 这个 co 库已经给我做了解决方案,我们来简单地看看它的实现的方式。
function co(it) {
// 我们最终返回一个Promise
return new Promise((resolve, reject) => {
// 循环回调,一直到generator最后done为false的时候,不能再迭代的时候
function step(data) {
// 迭代一次,获取当前步骤内容
let { value, done } = it.next(data)
if (!done) {
// 当前不知道具体步骤返回的值是多少,通过Promise.resolve(),得到then的链式调用内容,
Promise.resolve(value).then((data) => {
step(data)
}, reject)
} else {
// 当所有迭代走完,统一返回出去
resolve(value)
}
}
step()
})
}
下面我们使用 co()方法来修改读文件例子
function* getData() {
let path1 = yield fs.readFile('./path.txt', 'utf-8')
let name = yield fs.readFile(path1, 'utf-8')
return name
}
co(getData).then((data) => {
console.log(data) //圩上——TAO
})
噢噢噢噢哦哦哦哦哦哦~~~~
让我们继续看一眼这一段代码,是不是突然觉得很熟悉,是的,你想的没有错,就是 async + await
async + await
可以这么说 async + await === generator + co
async function getData() {
let path1 = await fs.readFile('./path.txt', 'utf-8')
let name = await fs.readFile(path1, 'utf-8')
return name
}
getData().then((data) => {
console.log(data)
})
// 圩上——TAO