生成器实现异步

js中实现异步有三大方式: 回调 Promise generator

  • 回调

    • 优势:简单、语义清晰
    • 劣势:回调地狱
  • Promise

    • 优势: 链式调用解决回调地狱、Promise.all 可以处理多个异步
    • 劣势:需要适用Promise语法
  • Generator

    • 优势:利用yield编写可读性更强、可以恢复和暂停、是async/await的底层原理、
    • 劣势:不是专门解决异步的,需要搭配Promise、要手动编写生成器和迭代器使得编码更复杂

现在我们来详细聊聊generator实现异步

场景

我要读一个文件dir.txt 这是一部目录,里面记录了 Lzy.json , XXX.json …. 等等 用户文件。

然后我们要读取Lzy.json 的信息。

async/await 方式解决

1
2
3
4
5
6
7
async function read() {
const dir = await readFile('./txt/dir.txt', 'utf8');
const userInfo = await readFile(path.resolve('./txt', dir), 'utf-8');
return userInfo
}

read().then(res=>console.log(res))

generator方式解决

1
2
3
4
5
6
7
8
9
10
11
12
13
function* read() {
const dir = yield readFile('./txt/dir.txt', 'utf8');
console.log(dir)
const userInfo = yield readFile(path.resolve('./txt', dir), 'utf-8');
console.log(userInfo)
return userInfo
}
const it = read() // 获取生成器对象
const res1 = it.next();
res1.value.then(res => {
// 相当于dir.txt完成后把值传给read函数的dir变量, 并执行一步生成器
it.next(res);
})

这样只执行了第一步,而我们得到第一步的PromiseResult之后要继续递归的运行next(PromiseResult) 这样才能得到第二步。

所以我们需要一个执行器 co

1
2
let finalRes= co(read())
finalRes.then(res=> console.log(res)) // 一步到位,输出task3完成时的PromiseResult

实现 co

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function co(gen) {
return new Promise((resolve, reject) => {
next();
function next(context) { // context 就是上一步异步任务完成的PromiseResult
const { value, done } = gen.next(context);
if (done)
resolve(value)
else
// Promsie.resolve(value) 为了防止value是普通值。
Promise.resolve(value).then(val => next(val))
}

})
}

context 就是上一步yield后面的表达式完成时的PromiseResult

例如: const dir = yield readFile('./txt/dir.txt', 'utf8'); 就是把context 赋值给了dir