async/await的前世
众所周知, js的异步主要是通过事件循环来实现的, 在平时我们可以用Promise来方便地进行一些异步操作.
function fetchData() {
return fetch('xxx')
.then(res => res.json())
.then(data => {
// do something
})
.catch(err => {
// do something
})
}
但是这样子不太方便阅读, 一般我们会使用async/await来让异步代码跟同步代码一样.
async function fetchData() {
try {
const res = await fetch('xxx')
const data = await res.json()
// do something
} catch (err) {
// do something
}
}
这样就跟同步代码一样, 不用看各种回调了.
这里在await的时候会"暂停"函数的执行, 等异步操作完成后再执行接下来的操作. 在之前的博客我应该介绍过一个也可以使得函数"暂停"的东西: 生成器.
function* generator() {
yield 1
console.log(1)
yield 2
console.log(2)
yield 3
console.log(3)
}
const iter = generator()
iter.next() // 什么都没有打印
iter.next() // 打印 1
iter.next() // 打印2
iter.next() // 打印3
每次调用next只会执行到最近的一个yield, 然后就暂停函数的执行, 直到下一次调用next.
同时这个next还可以传递参数, 比如:
function* gen() {
const res = yield 1
console.log(res)
const data = yield 2
console.log(data)
}
const iter = gen()
iter.next() // value: 1, done: false 什么都不打印
iter.next('res') // value: 2, done: false 打印 res
iter.next('data') // value: undefined, done: true 打印data
如果yield的是一个Promise, 那其实只要给Promise加个.then, 完成后把结果传递给下一个next就可以了. 大概这样:
// 随便写的一个, 模拟异步操作
function promise(i) {
return Promise.resolve(i)
}
function* gen() {
const res = yield promise(1)
console.log(res)
const data = yield promise(2)
console.log(data)
}
const iter = gen()
const { value: p1 } = iter.next()
p1.then((result1) => {
const { value: p2 } = iter.next(result1)
p2.then((result2) => {
iter.next(result2) // 这里done是true了
})
})
可以执行一下, 应该会打印出1和2. 根据这个, 就可以写出一个雏形出来了.
使用生成器来实现异步操作
没有错误处理
function exec(iter) {
return new Promise((resolve) => {
let result
const handle = () => {
const { value: p, done } = iter.next(result)
if (done) {
resolve(result)
return
}
// 注意yield的不是Promise的情况, 用Promise.resolve转化成Promise
Promise.resolve(p).then((res) => {
result = res
handle()
})
}
handle()
})
}
可以用这个代码来测试:
function promise(i) {
return Promise.resolve(i)
}
function* gen() {
yield 0
const res = yield promise(1)
console.log(res)
const data = yield promise(2)
console.log(data)
yield 3
}
const iter = gen()
exec(iter)
.then(console.log)
应该会打印出1, 2, undefined, 如果加个return 3的话那么就是打印1, 2, 3.
这样的话, 其实已经初见端倪了. function*
换成async function
, yield
换成await
, 是不是看上去就一样了?
加上错误处理
这里还没有写throw error的情况, 可以使用iter.throw.
iter.throw会把错误传递给生成器内部, 如果没能解决会抛出错误.
function* errGen() {
try {
yield 0
yield 1
} catch (error) {
yield 100
}
}
const iter = errGen()
console.log(iter.next()) // { value: 0, done: false }
console.log(iter.throw(new Error(1))) // { value: 100, done: false }
console.log(iter.next()) // { value: undefined, done: true }
这个iter.throw就相当与在里面加个throw. 这里的话调用了一次next, 那么就是在yield 1之前, yield 0之后插入一个throw.
try {
yield 0
throw new Error(1)
yield 1
} catch(err) {
yield 100
}
如果里面没有catch的话, 错误会继续往上抛. 现在来更新一下我们的exec.
function exec(iter) {
return new Promise((resolve, reject) => {
let result
let reason
const handle = () => {
let ne
if (reason) {
try {
ne = iter.throw(reason)
reason = null
} catch (error) {
reject(error)
return
}
} else {
ne = iter.next(result)
}
const { value: p, done } = ne
if (done) {
resolve(p)
return
}
// 注意yield的不是Promise的情况, 用Promise.resolve转化成Promise
Promise.resolve(p).then((res) => {
result = res
}).catch((err) => {
reason = err
}).finally(() => {
handle()
})
}
handle()
})
}
可以用这份代码来测试
function promise(i) {
return Promise.resolve(i)
}
function errPromise(i) {
return Promise.reject(i)
}
function* gen() {
try {
yield 0
const res = yield promise(1)
console.log(res)
const data = yield errPromise(2)
console.log(data)
yield 3
} catch (error) {
yield 4
}
}
const iter = gen()
exec(iter)
.then(console.log)
.catch(console.error)
应该会打印出1, undefined. 如果把try-catch去掉的话, 那么会被.catch捕获, 最终打印1, 2.
到这里的话其实功能是跟async/await差不多的了. 生成器部分的代码基本就是我们的业务代码, 无论如何都是要写的;而执行器的代码就需要自己去实现了. 貌似有个库叫co? 它就实现了一个执行器.
怎么说呢, 感觉还挺有意思的. 生成器我基本没用过, 也算是学习一下吧.
最终代码
function exec(iter) {
return new Promise((resolve, reject) => {
let result
let reason
const handle = () => {
let ne
if (reason) {
try {
ne = iter.throw(reason)
reason = null
} catch (error) {
reject(error)
return
}
} else {
ne = iter.next(result)
}
const { value: p, done } = ne
if (done) {
resolve(p)
return
}
// 注意yield的不是Promise的情况, 用Promise.resolve转化成Promise
Promise.resolve(p).then((res) => {
result = res
}).catch((err) => {
reason = err
}).finally(() => {
handle()
})
}
handle()
})
}