异常处理

学习这篇文章的笔记:# Callback Promise Generator Async-Await 和异常处理的演进

初 - 创建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基础语法

简言之:

  1. 我认为yield类似于"return",即return给外界某个值,只不过区别是return是真的到此为止了,yield后还可以通过next继续调用执行。

  2. 第1条说,"return给外界某个值",此值为下一次调用生成器函数的返回值。如:

    vbnet 复制代码
      const gen = 某个生成器函数。
      第一次调用:gen()
      遇到内部第一个 yield 1
      gen内部代码停止运行
      后续函数外部第一次调用next:const firstRes = gen.next()
      则,firstRes.value 是 gen 中第一个 yield 后面的值
  3. 函数外面传入的值 会成为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

另外:

  1. 生成器可以被迭代(🤔像数组一样用 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 都不会自动捕获,但好处是可以自动中断函数,我们大可放心编写业务逻辑,而不用担心异步异常后会被执行引发雪崩:

js 复制代码
function 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()
相关推荐
hzw051025 分钟前
使用pnpm管理前端项目依赖
前端
小柚净静29 分钟前
npm install vue-router 无法解析
javascript·vue.js·npm
风清扬雨40 分钟前
Vue3中v-model的超详细教程
前端·javascript·vue.js
高志小鹏鹏42 分钟前
掘金是不懂技术吗,为什么一直用轮询调接口?
前端·websocket·rocketmq
八了个戒44 分钟前
「JavaScript深入」一文说明白JS的执行上下文与作用域
前端·javascript
高志小鹏鹏1 小时前
Tailwind CSS都更新到4.0了,你还在抵触吗?
前端·css·postcss
qingyun9891 小时前
封装AJAX(带详细注释)
前端·ajax·okhttp
鱼樱前端1 小时前
前端工程化面试题大全也许总有你遇到的一题~
前端·javascript·程序员
小华同学ai1 小时前
331K star!福利来啦,搞定所有API开发需求,这个开源神器绝了!
前端·后端·github
记得坚持1 小时前
@monaco-editor/loader实现Monaco Editor编辑器
javascript·vue.js