异常处理

学习这篇文章的笔记:# 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()
相关推荐
mldong3 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi7 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu9 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss