zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Thinking--异步请求函数return应不应该加await?

2023-04-18 16:49:44 时间

Thinking系列,旨在利用10分钟的时间传达一种可落地的编程思想或解决方案。

在 codereview 代码中,发现了这样的两种写法。

写法一:

async function fn () {
  return await someAsyncReq()
}

写法二:

function fn () {
  return someAsyncReq()
}

有哪些区别呢?从写法上,直观可以看到的是

  • 写法一:返回的是执行结果(异步执行过程在 fn 函数内部)
  • 写法二:返回的是 Promise(异步执行过程在**调用 fn **函数的方法)

我们知道,调用 async 包裹的函数也需要通过 async...await 进行处理;同样的获取异步结果,也可以通过 async...await 处理,那么上述调用方式一直:

await fn()

从这个层面看,貌似我们可以忽略具体返回是 retrun promise 还是 return await promise 。这个结论,在一定场景下的确没有问题 – 异步函数没有异常抛出

/*写法一:示例*/
async function fn1 () {
   return await new Promise(reslove => setTimeout(() => reslove('ligang'), 1000))
}
/*写法二:示例*/
function fn2 () {
   return new Promise(reslove => setTimeout(() => reslove('ligang'), 1000))
}

await fn1()
await fn2()

上述执行结果一致,都会在 1000ms 后返回 “ligang”。

那么什么场景下,上述两种写法会有差异呢?

写法一:

async function fn () {
  try {
    return await someAsyncReq()
  } catch (err) {
    return await Promise.rejct('异步操作发生错误')
  }
}

写法二:

function fn () {
  try {
    return someAsyncReq()
  } catch (err) {
    return Promise.rejct('异步操作发生错误')
  }
}

当异步操作发生异常时,会有差异:

  • 写法一:会返回异常信息,即执行 catch 部分
  • 写法二:异常的捕获需要在调用的函数中处理,fn 函数中 catch 部分无法执行

如果仔细分析,相信大家可以得出相应的结论。

function promisedDivision(n1, n2) {
  if (n2 === 0) {
    return Promise.reject(new Error('Cannot divide by 0'))
  }
  return Promise.resolve(n1 / n2)
}


/*写法一:示例*/
async function fn1 () {
   try {
     return await promisedDivision(6, 0)
   } catch (err) {
     return err
   }
}
/*写法二:示例*/
function fn2 () {
   try {
     return promisedDivision(6, 0)
   } catch (err) {
     return err
   }
}

await fn1()	// Error: Cannot divide by 0
await fn2() // Uncaught Error: Cannot divide by 0

对于方式一,reject 的错误被成功捕获;对于方式二,reject 的错误被直接抛出了(Uncaught)。对于异常的处理,是提升代码鲁棒性的重要途径之一。且对错误未捕获,会导致程序终止执行。

结论

  • 如果当前场景,需要我们对错误统一处理,建议使用写法一 return await someAsyncReq()在函数内部统一处理
  • 如果当前场景,需要我们对错误差异化处理,建议使用写法二 return someAsyncReq()调用者可差异化处理
// return await promise
aysnc function _request(options) {
  try {
     return await axios(options)
  } catch (err) {
    let errorMsg = '请求发生了错误'
    if (err.code === 'ECONNABORTED') {
      errorMsg = '请求超时!'
    } else if (axios.isCancel(err)) {
      errorMsg = '请求被取消!'
    } else {
      switch (err.response.status) {
        case 401: // token失效
        case 403: // 无权限访问
        case 404: // 接口不存在
        case 500: // 接口错误
      }
    }
    return await Promse.reject({
      errorCode: err.response.status,
      message: `${errorMsg}:${err}`
    })
  }
}

// return promise
async function request (options) {
  return new Promise((resolve, reject) => {
    try {
      const {status, content} = _request(options)
      if (status === 'success'){
        resolve(content)
      } else {
        reject({
          errorCode: status,
          message: content
        })
      } 
    } catch (err) {
      reject(err)
    }
  })
}

项目中对 axios 请求经常会按照上述原则进行封装,axios 请求我们直接 return await promise 处理,便于对统一错误进行通用性处理(如401、403、500等),一致性强,减少不必要的冗余代码;而对于业务端我们采用 return promise 处理,如 status !== 'success' 情况,将错误抛出由调用者根据业务情况进行差异化处理,灵活度更高,更能契合业务需求。