优雅的使用 async与 await 处理 promise 的 reject

2,281 阅读4分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

异步

由于 javaScript 是单线程的,所以在程序中会有很多异步操作,比如我们在使用ajax获取数据的时候,比如回调函数。

假如现在我正在完成的项目里有这样一个需求:

  • 首先调用一个获取表格数据的请求
  • 基于这个请求返回的数据获取其中一个数据的详情

这种好像我们都写过,这里我直接使用 axios 这个 ajax 库

axios.get('/api/table_list')
    .then(res=>{
            let id = res[0].tableId
            axios.get('/api/table_detail?id='+id)
                    .then(res=>{
                            randerToView(res)
                    })
                    .catch(err=>handelErr(err))
    })
    .catch(err=>handelErr(err))

function randerToView(object){}
function handelErr(err){}

这样的代码读起来很费眼睛,写代码时的逻辑也要跟的上才行,所以这并不友好,简称回调地狱。

好在有 promise 这个 API。

promise

ECMAscript 6 提供了 Promise 这样一个 API。它代表了未来将要发生的事件,因此用来传递异步操作的结果。

这个 API 最大的好处,我认为是它避免了上述回调地狱的写法。因为 Promise 的三个状态,可以让它将层层嵌套的回调函数,用同步的写法表达出来。

function getTableList(){
    return new Promise((resolve, reject)=>{
        axios.get('/api/table_list').then(_=>{
            resolve(_[0].tableId)
        })
    })
}

function getTableDetailById(id){
    axios.get('/api/table_detail?id='+id).then(_=>{
        randerToView(_)
    })
}

getTableList.then(getTableDetailById)

与直接使用异步回调相比,这样的写法虽然代码多了一些,但是明显感到代码的逻辑更符合我们日常的思考习惯。

而且往往在程序中,并非代码写的越少越好。

因为越少的代码需要越深入的思考,不仅浪费脑细胞,等过上一段时间,如果没有注释,你很可能已经把当时的思考忘的一干二净,从而带来维护的困难。

async 和 await

asyncawait,可以让代码可读性得到巨大的提升,也能让代码量减少到惊人的程度。这要归功于 ECMAscript 7 的出现。

比如还是上面的需求,使用 asyncawait

(asycn ()=>{
    let res = await axios.get('/api/table_list')
    let id = res[0].tableId
    let detail = await axios.get('/api/table_detail?id='+id)
    randerToView(detail)
})()

所以跟多人都喜欢在编程中大量的使用asyncawait

但是这样写也有个问题,就是我们不能在异步发生错误的时候,捕捉到该错误,因此,代码应该放在try{}catch(){}语句中

try{
    let res = await axios.get('/api/table_list')
    let id = res[0].tableId
    let detail = await axios.get('/api/table_detail?id='+id)
    randerToView(detail)
}catch(err){
    handelErr(err)
}

这段代码看上去还 ok,但是写得多了,仔细想想,这个 try...catch...感觉也不是很优雅。难道不能有一种解决方案来满足程序员的小洁癖吗?

分析

假如我们把axios.get('/api/table_list')打印出来,会发现它是一个Promise,而await只能拿到resolve状态,因此对于reject是无能无力的。

try...catch...语句则可以捕获其代码块内部的任何错误,因此可以 catch 到reject状态。

所以我们是不是可以尝试自己实现一个方法

假如我们先捕获 Promise 的 Error ,然后将此 Error 和正常的数据都放进另一个 Promise 的 resolve 中,再传递给 await ,这样,后一个 Promise 的 await 就有了正常数据和 Error 两个状态。

实现

export handlerPromise = (promise) => promise.then(data => [null, data]).catch(err => [err])

验证

在程序中,处理删除是非常谨慎的,通常我们要弹出一个确认框提示用户是否要删除数据。

elementUI 中的 Message 模块提供了完善的实现。

每一个页面可能都有需要用户删除的数据,那我们就可以抽取一个确认弹框,作为公共的方法。

// val 是要删除的数据,type 为 Array
export const delDialog = val =>
  new Promise((resolve, reject) => {
    if (!val.length) {
      Message.info('请选择要删除的数据')
      reject(new Error())
    } else {
      MessageBox.confirm('将删除选中的数据,确定删除吗?', '提示', {
        cancelButtonText: '取消',
        confirmButtonText: '确定',
        type: 'warning'
      }).then(() => {
        resolve(val)
      }).catch((action) => {
        reject(action)
      })
    }
  })

在需要使用的页面引入,就可以了。

import { delDialog , handlerPromise } from '@/utils/del'

...

methods:{
    handleRemove(){
            let [dialogErr, res] = await handlerPromise(delDialog (this.multipleSelection))
            if(dialogErr) return

            let res = await tableDelete(res)
            //因为ajax错误已经在顶层做了捕捉和提示用户,所以在这里直接 return 就可以了
            if(!res) return

            this.getTableList()
    }
}

貌似确实方便了不少,如果对你有所帮助,就帮我点个赞吧~