初 - 创建promise的时候
promise
,不论在定义的时候、还是 .then
处理的时候,里面的throw
、 reject
都可以在.catch
中捕获到。
不过,如果在 promise
中的 某个异步处理中,比如setTimeout(()=>{ throw.error })
,在后面的.then()
中就不能捕获到。不过如果是在setTimeOut
里面 reject
还是可以被捕获到的。
下面这种是可以
的:
js
function fetch(callback) {
return new Promise((resolve, reject) => {
// 1、
throw Error('用户不存在')
// 或者 2、
// reject('用户不存在')
// 或者 3、
// setTimeOut(()=>{
// reject('用户不存在')
// })
})
}
fetch().then(result => {
console.log('请求处理', result) // 永远不会执行
}).catch(error => {
console.log('请求处理异常', error) // 请求处理异常 用户不存在
})
这种是不可以
的
js
function fetch(callback) {
return new Promise((resolve, reject) => {
setTimeOut(()=>{
throw Error('用户不存在')
})
})
}
fetch().then(result => {
console.log('请求处理', result) // 永远不会执行
}).catch(error => {
console.log('请求处理异常', error) // 永远不会执行
})
简言之,宏任务
中,抛出异常,不会被 promise 捕获。
不过这样有一个"好处",虽然因为不在一个调用栈中导致不会被捕获,但也不会影响后续代码执行。
续 - 在promise.then里面抛出异常
微任务中可以抛出异常,可以被当前作用域捕获到
js
Promise.resolve().then(() => {
throw new Error('这是一个Promise中的异常');
}).catch(error => {
console.error('捕获到了异常:', error);
});
在微任务中的宏任务中抛出异常怎么办(无法捕获的异常
如果第三方函数在
macrotask
(宏任务) 回调中以throw Error
的方式抛出异常怎么办? 在promise.then
中的宏任务中reject
:
js
// 第三方函数
function thirdFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('收敛一些')
})
})
}
Promise.resolve(true).then((resolve, reject) => {
return thirdFunction()
}).catch(error => {
console.log('捕获异常', error) // 捕获异常 收敛一些
})
请注意,如果
return thirdFunction()
这行缺少了return
的话,依然无法抓住这个错误,这是因为没有将对方返回的Promise
传递下去,错误也不会继续传递。
return
一个被reject
、并且 reject未被处理的promise
,后续的promise.catch就会处理这段个reject。
如果加一层.then
接收就可以更明晰的看到:
正常处理下的返回值走 then
路线、
异常处理走catch
路线:
我们发现,这样还不是完美的办法,不但容易忘记
return
,而且当同时含有多个第三方函数时,处理方式不太优雅:
js
Promise.resolve(true).then((resolve, reject) => {
return thirdFunction().then(() => {
return thirdFunction()
}).then(() => {
return thirdFunction()
}).then(() => {
})
}).catch(error => {
console.log('捕获异常', error)
})
番外
更优雅的处理方式 - Generator
首先了解一下Generator基础语法
简言之:
-
我认为
yield
类似于"return"
,即return给外界某个值,只不过区别是return是真的到此为止了,yield后还可以通过next继续调用执行。 -
第1条说,"return给外界某个值",此值为下一次调用生成器函数的返回值。如:
vbnetconst gen = 某个生成器函数。 第一次调用:gen() 遇到内部第一个 yield 1 gen内部代码停止运行 后续函数外部第一次调用next:const firstRes = gen.next() 则,firstRes.value 是 gen 中第一个 yield 后面的值
-
函数外面传入的值 会成为
yield
的返回值vbnet接上,外界调用 gen.next() 时,传值。 即 gen.next(1) 内部接收 const secArg = yield 2 则 secArg 值为 gen.next(1) 的入参: 1.
看透这段代码就能理解上面三点。
js
function* generator(count) {
const firstArg = count
console.log('第一次调用gen传入的值记作第一个参数:', firstArg) // next0
const secArg = yield 'yield1'
console.log('第一次调用gen.next()的入参:', secArg) // next2
yield 'yield2'
}
// 记 gen
const gen = generator('next0')
const firstRes = gen.next('next1')
console.log('第一个yield返回值:', firstRes.value) // yield1
const secRes = gen.next('next2')
console.log('第二个yield返回值:', secRes.value) // yield2
另外:
- 生成器可以被迭代(🤔像数组一样用
for
循环。详见:MDN中生成器那页第一个例子
扩展 - 用 Generator 模拟 async/await 的实现效果
async await 是 generator 的语法糖
js
// 返回一个 promise ( 延时 time 时间后才会 resolve
const timeOut = (time = 0) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(time + 200)
}, time)
})
// generator 函数 每次执行一个 timeOut ,同时打印该 timeOut 的返回值
function* main() {
const result1 = yield timeOut(200)
console.log(result1)
const result2 = yield timeOut(result1)
console.log(result2)
const result3 = yield timeOut(result2)
console.log(result3)
}
// 返回一个函数, 该函数返回一个 promise
function step(generator) {
const gen = generator()
// 由于其传值,返回步骤交错的特性,记录上一次 yield 传过来的值,在下一个 next 返回过去
let lastValue
// 包裹为 Promise,并执行表达式
// 闭包
return () => Promise.resolve(gen.next(lastValue).value).then(value => {
// 由于这个匿名函数里面读取了lastValue,导致lastValue不会被释放,且每次执行 step() 的时候都会读取 lastValue。
// 下面这行更新外部 lastValue 的值
lastValue = value
return lastValue
})
}
// 利用生成器,模拟出 `await` 的执行效果
const run = step(main)
function recursive(promise) {
promise().then(result => {
if (result) {
recursive(promise)
}
})
}
recursive(run)
// 400
// 600
// 800
async/await 异常
不论是同步、异步的异常,
await
都不会自动捕获,但好处是可以自动中断函数,我们大可放心编写业务逻辑,而不用担心异步异常后会被执行引发雪崩:
jsfunction fetch(callback) { return new Promise((resolve, reject) => { setTimeout(() => { reject() }) }) } async function main() { const result = await fetch() console.log('请求处理', result) // 永远不会执行 } main()
我们都知道(如果不知道,那现在就知道了):
await 后面的代码其实就是在
promise.then() 里的代码
。
换句话说 :async/await 是 promise 的语法糖
可想而知,await 后面的代码如果发生异常, await 的处理方式和 promise.then() 的处理方式是一样的。
Async Await 捕获异常
野路子:await 后面 catch 一下
如果想要执行,可以在后面接一个catch来做错误处理,当然这样并不优雅,如果有很多 await
,每个都要加一个 catch
吗?很不优雅。
正常捕获:try、catch
js
function fetch(callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('no')
})
})
}
// 只有这边加一个 try/catch 来处理
async function main() {
try {
const result = await fetch()
console.log('请求处理', result) // 永远不会执行
} catch (error) {
console.log('异常', error) // 异常 no
}
}
main()
async/await 无法捕获的异常
既然 async/await 是 promise 的语法糖
,
那么 " promise 无法捕获的异常 " 就是 " async/await 无法捕获的异常 " ;
同时 "解决 promise 无法捕获的异常的处理办法 " 就是 "解决 async/await 无法捕获的异常的处理办法 "
回顾一下 promise 无法捕获的异常:
调用第三方函数时,第三方函数在异步任务中出现异常我们需要将此第三方函数 return 出去:
上图右边代码在此:
js
function thirdFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('收敛一些')
})
})
}
async function main() {
try {
const result = await thirdFunction()
console.log('请求处理', result) // 永远不会执行
} catch (error) {
console.log('异常', error) // 异常 收敛一些
}
}
main()
至于说 await 会比 promise 更优雅。
当然是代码可读性更好、对开发更友好:
代码:
js
async function main() {
try {
const result1 = await secondFunction() // 如果不抛出异常,后续继续执行
const result2 = await thirdFunction() // 抛出异常
const result3 = await thirdFunction() // 永远不会执行
console.log('请求处理', result) // 永远不会执行
} catch (error) {
console.log('异常', error) // 异常 收敛一些
}
}
main()