返回

Async/Await vs .then().catch(): 最佳实践指南

javascript

Async/Await 与 .then().catch() 的混合使用

混合使用 async/await.then().catch() 常常引发关于代码可读性和潜在问题的讨论。这两种异步处理机制,虽各有优势,但在不当结合时会带来一些意想不到的情况。理解其背后的原理至关重要,能帮助我们写出更健壮、易于维护的代码。

问题分析

async/await 建立在 Promise 的基础之上,它提供了一种更同步化的编写异步代码的方式。通过 await ,我们可以暂停一个异步函数的执行,直到 Promise resolve 或 reject,让代码更接近同步执行的逻辑,更方便我们跟踪调试,减少回调地狱的情况。.then().catch() 是 Promise 对象本身的方法,用于处理 Promise 的成功(resolve)和失败(reject)状态。

上述示例中的混合用法,表面上看似乎是在处理一个异步操作,并通过 .then() 对结果做转换, .catch() 来处理错误,同时在外部使用 async/await 包裹。 这样使用的风险主要体现在错误处理和代码结构上。一个问题是,如果 .then() 内部抛出异常,该异常不会被外层的 try/catch 块捕获。错误处理的逻辑散落在 .then().catch() 和外层的 async 函数中,不易集中管理,且在层级更深的异步操作中会更复杂。

解决方案与实践

我们有多种策略来解决这种混合使用带来的问题,核心目标是将错误处理统一,提高代码的清晰度。

1. 完全使用 Async/Await 的错误处理

首选方案是将 .then().catch() 替换为 async/awaittry/catch 结构。这是因为使用 try...catch 代码会更简洁,代码块的功能边界也会更明显,这在复杂场景中会有巨大优势。

操作步骤:

  1. 移除 .then() 方法及其内部逻辑,将其转换为一个独立的异步操作或同步的计算函数。
  2. 移除 .catch() 方法。
  3. await 表达式外层包裹一层 try...catch 代码块。
  4. 将错误处理的逻辑放入 catch 代码块中。

代码示例:

async apiCall(params) {
    let results;
    try {
       const response = await this.anotherCall(); // 直接 await
        results = transformResults(response) //结果处理移动到单独的函数或者独立处理的逻辑中
    } catch (error) {
        // 在这里处理所有错误,例如记录日志或展示用户提示
        console.error("Error occurred:", error);
        results = null // 或提供一个默认值
    }
    return results;
}

function transformResults(results){
    //任何results的转换操作都写在这里,最好是一个无副作用的纯函数。
    return results;
}

使用try...catch能将整个异步操作,从调用到数据处理的各个阶段全部都包裹起来,方便定位问题发生点,将所有异步操作统一管理。

2. 使用封装的 Promise 处理方法

有时候,我们会碰到一些既有的使用.then().catch() 的promise操作,想要在 async/await 的环境中使用。此时可以编写一个中间函数进行promise操作的封装,让调用侧只面对简单的await表达式。这种方法的核心是将异步操作封装到可复用的模块中,有利于代码的整洁和复用性。

操作步骤:

  1. 创建名为 wrapPromise 的函数,参数为一个 Promise 。
  2. 该函数内部用 try/catch 结构包装传递进来的 Promise。
  3. 如果 Promise 成功 resolve,返回 resolve 的结果。如果失败 reject,则记录错误,并返回 null 或一个默认值。
  4. async函数中使用 await wrapPromise(promise) 的形式进行异步操作,调用侧不在使用 .then().catch()

代码示例:

async function wrapPromise(promise){
  try{
    return await promise;
  }catch(e){
    console.error("Error during promise execution:",e)
     return null; //或其它默认值
  }
}


async apiCall(params) {
    const response =  this.anotherCall();
    const transformedResponse =  await wrapPromise(response) //直接使用await处理

    //可以进行基于返回结果的其他业务逻辑
    return transformedResponse
}

此方法在维持错误处理逻辑的前提下,可以让异步操作的使用方式更为统一。

3. 对 .then().catch() 的操作做进一步封装。

可以把 .then().catch() 部分作为一个函数独立出去,在 async/await 的函数内部直接调用,避免同时出现两套机制处理异步的问题,这个策略在应对复杂的业务逻辑和需要复用的情况下非常有用。

操作步骤:

  1. 创建一个包含.then().catch()的函数,并接收promise对象和参数。
  2. 在该函数内部进行异步操作和相应的错误处理。
  3. async 函数内部调用该函数处理异步结果。

代码示例:

async function handleAsyncOperation(promise) {
  return promise
    .then(results => {
      //  结果处理
      return results;
    })
    .catch(error => {
        console.error("Error:", error);
         //错误处理
      return null;
    });
}

async apiCall(params) {
  const results = await handleAsyncOperation(this.anotherCall());

  return results;
}

此策略强调函数的功能边界清晰,且在多个 async 函数中需要执行相似 .then().catch() 逻辑的时候,提高了代码的复用性和一致性。

总结

使用 async/await.then().catch() 混合编程时需小心谨慎。使用 async/await 搭配 try/catch 进行错误管理是较好的方案,或考虑通过封装 Promise、封装 .then().catch() 部分逻辑等手段提高代码的可读性和维护性。务必在团队内达成一致,明确异步代码的处理方式,这样才能更好地驾驭这两种异步处理模式。

确保代码的一致性,有利于构建稳固且可扩展的应用。 选择哪种方法需要根据具体情况进行考虑。 最终目标是提升代码可读性和降低代码维护难度。