async/await解决什么问题:
async/await可以解决promise.then语法链式调用的繁琐问题,通过.next()传参形式,把表达式的值重新注入到函数里面,可以像同步调用那样拿到异步函数的结果值,在调用generator函数时,里面的函数也不会全部执行,而是需要.next()一步一步运行到yield的节点,这样在yield表达式下的函数,就需要等到外部函数调用.next()之后调用,这样申明和调用函数的逻辑就可以写在一起。这个是通过Iterator无法实现的(下文会详细解释)
P.S.: 任何有疑问的地方都可以通过debugger来打断点去理解。通过npm run debug
前置知识:
async/await是generator/yield的语法糖
generator/yield[扩展阅读:generator/yield是什么?]
- 语法:
// 声明一个Generator函数
function* gen () {
const result = yield 表达式
}
// 调用
const g = gen()
g.next(param)
g.throw(param)
g.return(param)
next/throw/return能让我们从外面向generator传值
next 执行yield的表达式,并得到{done ,value}对象,使外层函数知道generator是否已经执行完,yield的表达式求值后的结果是什么,next传递的参数,赋值给result
throw可以让generator抛出异常,终止generator,传递错误参数,会被内部的catch捕获使用
return可以让generator终止,value值为return传的参数
generator/yield提供了一种外部函数和内部函数传递数据的方式,实现逻辑分离,那么一步步调用next函数比较麻烦的,有没有一种自动执行的方法,自动执行就是通过递归并传递参数的形式实现的
那么我们先实现一个同步版本的自动执行器
// 实现async,await
// 先考虑generation yield的同步版本
function* g() {
const a = yield syncFn('hi')
console.log('a', a)
const b = yield syncErrorFn(a + 'sync')
console.log('a', b)
return b
}
function syncFn(a) {
return a
}
function syncErrorFn(a) {
throw(new Error(a))
}
function syncStep(gen) {
const g = gen()
function autoRun(property, preVal) {
let next;
next = g[property](preVal)
if(next.done) {
return next.value
}else{
return autoRun('next', next.value)
}
}
try {
return autoRun('next', undefined)
} catch (error) {
console.log('outer', error.message)
}
}
syncStep(g)
这里是同步调用,我们不需要担心抛错能不能被冒泡捕获,yield表达式如果抛错,generator函数就会终止迭代,理解了同步调用,那么异步调用只需要解决好promise.then的问题,把resolve的value返回到generator函数,或者reject的error带到函数终止迭代就可以了。
// 实现异步版本
function* asyncG() {
const a = yield asyncFn('hi')
console.log('a', a)
const b = yield asyncErrorFn(a + 'async')
debugger
console.log('b', b)
return b
}
function asyncFn(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a)
},1000)
})
}
function asyncErrorFn(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(a)
},500)
})
}
function step(gen) {
const g = gen()
return new Promise((resolve, reject) => {
function autoRun(property, preVal) {
let next;
try {
next = g[property](preVal)
debugger
} catch (error) {
return reject(error) // 这里reject的同时需要终止递归
}
if(next.done) {
return resolve(next.value)
}
next.value.then((resolve) => {
autoRun("next", resolve)
}).catch((error) => {
autoRun("throw", error)
})
}
autoRun('next', undefined)
})
}
step(asyncG).then((resolve) => {
console.log('step', resolve)
}).catch((error) => {
console.log('stepError', error)
})
那我们再看看细看处理错误的问题,我们有时会遇到UnhandledPromiseRejectionWarning,这是哪里出问题了呢?
我们不再.catch捕获async返回的promise异常改成try/catch形式
async function canNotCatchFn() {
try {
// UnhandledPromiseRejectionWarning
step(asyncG) // 这里会捕获不了错误,要不就是再包async,await,要不就.catch去捕获
// await step(asyncG)
} catch (error) {
console.log(error)
}
}
canNotCatchFn()
这里由于async内部有错误,而内部也没有try/catch去捕获,那么这个错误就会被autoRun执行的时候捕获到,然后将返回的promise的状态设为reject,而外部调用的时候没有处理这个reject就会导致UnhandledPromiseRejectionWarning,知道什么原因之后,要解决就很简单要不再包async,await,要不就.catch去捕获。如果在内部有try/catch去捕获,就不会走到autoRun的catch,而是继续执行到下一个yield。
那么这里总结一下错误处理:
- 写在async/await内部,try/catch时需要注意,返回的值可能是undefined,因为reject的时候是拿不到值的,而try/catch之后的yield能正常调用【适用如果其中有一步异步出错,但下一步异步操作仍正常执行】
- 写在调用async的时候,这时候函数只会走到抛出异常以前的await,得到的catch也是出异常的异步函数时抛出的reject【适用如果其中有一个异步出错了,就不行继续其他异步】
- 可以不过早的捕获错误,写service层调用服务的时候,可以将捕获错误留到调用这个服务才写
- 如果要用async/await内部捕获就要try/catch,直接调用async就要用.catch捕获,async函数如果没写try/catch想捕获同步错误,需要在外部调用的时候用.catch
// 模拟service情况
const getEntityCatch = async() => {
try {
const data = await getFailEntity()
return data
} catch (error) {
console.log('getEntityError', error)
}
}
const getFailEntity = async() => {
const data = await mockFetchData(false)
return data
}
const getSuccessEntity = async() => {
const data = await mockFetchData(true)
return data
}
const mockFetchData = (success) => {
return new Promise((resolve, reject) => {
if(success) {
resolve([1,2,3])
}else{
reject(new Error('fail'))
}
})
}
// 不相关的步骤想顺序执行
const uiNotRelativeFn = async() => {
const data1 = await getEntityCatch()
console.log('uiNotRelativeFn', data1)
const data2 = await getSuccessEntity() // 注意这里抛出的错误没有被捕获
console.log('uiNotRelativeFn', data2)
}
uiNotRelativeFn() // 如果data2抛出错误,则会捕获不到data2的错误
// 相关的步骤想顺序执行
const uiRelativeFn = async() => {
try {
const data1 = await getFailEntity()
console.log('uiRelativeFn', data1)
const data2 = await getSuccessEntity()
console.log('uiRelativeFn', data2)
} catch (error) {
console.log('uiRelativeFnError', error)
}
}
uiRelativeFn()
// 相关的步骤想顺序执行
const uiRelativeNotCatchFn = async() => {
const data1 = await getFailEntity()
console.log('uiRelativeNotCatchFn', data1)
const data2 = await getSuccessEntity()
console.log('uiRelativeNotCatchFn', data2)
}
const uiRelativeNotCatchNormalErrorFn = async() => {
const data2 = await getSuccessEntity()
console.log('uiRelativeNotCatchFn', data2)
throw(new Error("sync Error"))
const data1 = await getFailEntity()
console.log('uiRelativeNotCatchFn', data1)
}
uiRelativeNotCatchNormalErrorFn().catch(error => {
console.log('uiRelativeNotCatchNormalErrorFn', error)
})
下面来探讨一下如果不用generator,单纯通过Iterator方式要怎么实现
我们知道generator返回的就是一个迭代对象,那我们如果不用generator,能不能也实现同样的效果
我们来先看一般我们是怎么实现promise的顺序执行的
// promise版本
function promiseFn() {
return asyncFn("hi")
.then((resolve) => asyncFn(resolve + "async"))
.then((resolve) => asyncErrorFn(resolve))
}
promiseFn().then((resolve) => {
console.log('promiseFn', resolve)
}).catch((error) => {
console.log('promiseFnError', error)
})
如果我们想免去promise.then语法让他顺序执行,那么这些备调用的函数需要存在generator函数里面。我们用数组来存储他们
// 自动执行promise版本
function autoRunPromiseFn(arr) {
return new Promise((resolve, reject) => {
function autoRun(value) {
const promiseFn = arr.shift()
debugger
if(!promiseFn)return resolve(value)
promiseFn(value).then((resolve) => {
autoRun(resolve)
}).catch((error) => {
reject(error)
})
}
autoRun(undefined)
})
}
autoRunPromiseFn([() => asyncFn("hi"), (resolve) => asyncFn(resolve + "async"), (resolve) => asyncErrorFn(resolve)]).then((resolve) => {
console.log('autoRunPromiseFn', resolve)
}).catch((error) => {
console.log('autoRunPromiseError', error)
})
显然这样用数组,并没有比一般promise好多少,只是把promise.then这个去掉了,看起来还是比较复杂,那么我们用一个generator函数包含他们
// Iterator promise版本
const promiseNextFn = () => {
let promiseFnArr = []
let error = false
return {
next(...args){
const promiseFn = promiseFnArr.shift()
if(!promiseFn || error){
return {
value: args,
done: true
}
}
return {
done: false,
value: promiseFn(...args)
}
},
setCurrentFn(fn){
promiseFnArr.push(fn)
},
throw(value){
error = true
throw(new Error(value))
}
}
}
function promiseRunFn() {
const g = promiseNextFn()
g.setCurrentFn(() => {
return asyncFn("hi")
})
g.setCurrentFn((resolve) => {
return asyncFn(resolve + "async")
})
g.setCurrentFn((resolve) => {
return asyncErrorFn(resolve)
})
return g
}
step(promiseRunFn).then((resolve) => {
console.log('promiseFn', resolve)
}).catch((error) => {
console.log('promiseFnError', error.message)
})
这里我们直接复用之前实现的async/await的step,但是没有了generator/yield,我们就要把声明异步函数和调用异步函数的逻辑分开,最后只能跟promise.then一样,但通过这个Iterator版本你会对generator有更深的了解,比如为什么要先执行一次generator函数,为什么会返回一个迭代对象,为什么抛错可以在generator内部和外部都能获取到
reference:
- https://github.com/cyanxxx/blogcode/tree/master/how_to_realize_aysnc_await[本文所用的代码]
- https://es6.ruanyifeng.com/#docs/generator [es6的generator]