JS `async/await` 异常处理:`try-catch` 与 `Promise.prototype.catch()` 的最佳实践

各位老铁,大家好!今天咱们聊聊 JavaScript 里 async/await 这哥俩的异常处理,保证让各位听完之后,再也不用担心代码动不动就崩给你看了。

开场白:别让 async/await 变成你的噩梦

async/await 简化了异步代码的编写,让代码看起来更像同步代码,提高了可读性。但是,如果不对异常进行妥善处理,它也会变成你的噩梦,让你的程序时不时给你来个惊喜的崩溃。

try-catch:最直接的异常捕获方式

try-catch 块是处理异常的最基本也是最常用的方法。它允许你将可能抛出异常的代码包裹在一个 try 块中,并在 catch 块中处理这些异常。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('出错了:', error);
    // 在这里进行错误处理,比如显示错误信息,重试请求等
    return null; // 或者抛出新的错误,取决于你的需求
  }
}

async function processData() {
    const data = await fetchData();
    if(data){
        console.log("数据处理结果:", data);
    } else {
        console.log("数据获取失败,无法处理。");
    }
}

processData();

在这个例子中,fetchData 函数使用 try-catch 块来捕获 fetch 请求和 response.json() 解析过程中可能发生的任何错误。如果在 try 块中的任何地方发生错误,控制权将立即转移到 catch 块,在那里你可以处理错误并采取适当的措施。

Promise.prototype.catch():另一种选择

Promise.prototype.catch() 方法是另一种处理 async/await 函数中异常的方式。它允许你为 Promise 对象添加一个错误处理程序。

async function fetchData() {
  return fetch('https://api.example.com/data')
    .then(response => response.json())
    .catch(error => {
      console.error('出错了:', error);
      // 在这里进行错误处理
      return null; // 或者抛出新的错误
    });
}

async function processData() {
    const data = await fetchData();
    if(data){
        console.log("数据处理结果:", data);
    } else {
        console.log("数据获取失败,无法处理。");
    }
}

processData();

在这个例子中,fetchData 函数使用 Promise.prototype.catch() 方法来捕获 fetch 请求和 response.json() 解析过程中可能发生的任何错误。 注意 catch 是链式调用.then() 之后添加的。

try-catch vs. Promise.prototype.catch():该选哪个?

这两种方法都可以用来处理 async/await 函数中的异常,但它们在一些方面有所不同。

特性 try-catch Promise.prototype.catch()
适用范围 适用于同步和异步代码 仅适用于 Promise 对象
语法 更加结构化,易于阅读和理解 更加分散,可能需要链式调用
错误处理位置 可以在 try 块中立即处理错误 错误处理程序在 Promise 对象解决后才执行
适用场景 需要在特定代码块中处理错误时 需要为整个 Promise 链添加一个全局错误处理程序时
可读性 通常比 Promise.prototype.catch() 更容易理解和维护 在简单的 Promise 链中可能更简洁
嵌套的 async/await 处理嵌套的 async/await 错误更方便 需要在每个 Promise 链中添加 catch,否则错误可能会被忽略

最佳实践:结合使用,各取所长

一般来说,推荐结合使用 try-catchPromise.prototype.catch() 来处理 async/await 函数中的异常。

  • 使用 try-catch 块来捕获函数内部的错误。 这可以让你在特定的代码块中处理错误,并采取适当的措施。
  • 使用 Promise.prototype.catch() 方法来为整个 Promise 链添加一个全局错误处理程序。 这可以确保即使在 try-catch 块中没有捕获到错误,也能得到处理。
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('fetchData 出错了:', error);
    throw error; // 重新抛出错误,让外层函数处理
  }
}

async function processData() {
  try {
    const data = await fetchData();
    console.log("数据处理结果:", data);
  } catch (error) {
    console.error('processData 出错了:', error);
    // 全局错误处理,比如显示错误页面,记录错误日志等
  }
}

processData(); // 这里就不需要 catch 了,因为 processData 内部已经 catch 了

在这个例子中,fetchData 函数使用 try-catch 块来捕获错误,并将错误重新抛出。processData 函数使用 try-catch 块来捕获 fetchData 函数抛出的错误,并进行全局错误处理。

更复杂的例子:多个 await 调用

async 函数中包含多个 await 调用时,错误处理变得更加重要。你需要确保每个 await 调用都得到妥善的保护。

async function processMultipleRequests() {
  try {
    const result1 = await apiRequest1();
    const result2 = await apiRequest2(result1); //依赖 result1
    const result3 = await apiRequest3(result2); //依赖 result2

    console.log('所有请求都成功完成:', result3);
    return result3;

  } catch (error) {
    console.error('发生错误:', error);
    // 在这里进行错误处理,例如回滚事务,发送警报等
  }
}

async function apiRequest1() {
  try {
    const response = await fetch('https://api.example.com/api1');
    if (!response.ok) {
      throw new Error(`请求 1 失败:${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('apiRequest1 失败:', error);
    throw error; // 重新抛出错误
  }
}

async function apiRequest2(data1) {
  try {
    const response = await fetch('https://api.example.com/api2', {
      method: 'POST',
      body: JSON.stringify(data1)
    });
    if (!response.ok) {
      throw new Error(`请求 2 失败:${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('apiRequest2 失败:', error);
    throw error; // 重新抛出错误
  }
}

async function apiRequest3(data2) {
  try {
    const response = await fetch('https://api.example.com/api3', {
      method: 'PUT',
      body: JSON.stringify(data2)
    });
    if (!response.ok) {
      throw new Error(`请求 3 失败:${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('apiRequest3 失败:', error);
    throw error; // 重新抛出错误
  }
}

processMultipleRequests();

在这个例子中,processMultipleRequests 函数包含多个 await 调用。每个 await 调用都包裹在一个 try-catch 块中,以捕获可能发生的错误。如果任何一个 await 调用失败,错误将被捕获,并进行相应的处理。 每个 apiRequest 函数都将错误向上抛出,最终由 processMultipleRequestscatch 块处理。

错误处理策略:具体情况具体分析

错误处理策略应该根据你的具体需求来制定。以下是一些常见的错误处理策略:

  • 重试请求: 如果请求失败,可以尝试重新发送请求。这对于网络连接不稳定等临时性错误非常有用。

    async function fetchDataWithRetry(url, maxRetries = 3) {
      for (let i = 0; i < maxRetries; i++) {
        try {
          const response = await fetch(url);
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          return await response.json();
        } catch (error) {
          console.error(`Attempt ${i + 1} failed:`, error);
          if (i === maxRetries - 1) {
            throw error; // 所有重试都失败,抛出错误
          }
          await new Promise(resolve => setTimeout(resolve, 1000)); // 等待 1 秒后重试
        }
      }
    }
  • 显示错误信息: 如果请求失败,可以向用户显示错误信息。这可以帮助用户了解发生了什么问题,并采取相应的措施。

    async function fetchDataAndDisplayError() {
      try {
        const data = await fetchData('https://api.example.com/data');
        console.log('数据:', data);
      } catch (error) {
        alert('获取数据失败:' + error.message);
      }
    }
  • 回滚事务: 如果在事务中发生错误,可以回滚事务,以确保数据的一致性。

    async function processTransaction() {
      try {
        await startTransaction();
        await updateDatabase();
        await commitTransaction();
        console.log('事务成功完成');
      } catch (error) {
        console.error('事务失败:', error);
        await rollbackTransaction();
        console.log('事务已回滚');
      }
    }
  • 发送警报: 如果发生严重的错误,可以发送警报,通知管理员。

    async function processDataAndAlert() {
      try {
        const data = await fetchData();
        // ... 处理数据
      } catch (error) {
        console.error('处理数据失败:', error);
        await sendAlert('数据处理失败:' + error.message);
      }
    }
  • 记录错误日志: 记录错误日志可以帮助你诊断和解决问题。

    async function fetchDataAndLogErrors() {
      try {
        const data = await fetchData();
        // ... 处理数据
      } catch (error) {
        console.error('获取数据失败:', error);
        await logError('获取数据失败:' + error.message);
      }
    }

一些额外的建议

  • 不要吞噬错误。 除非你确定能够完全处理错误,否则不要吞噬错误。吞噬错误会使你难以诊断和解决问题。 重新抛出错误,或者返回一个包含错误信息的值。
  • 使用有意义的错误信息。 错误信息应该能够帮助你了解发生了什么问题,以及如何解决问题。
  • 测试你的错误处理代码。 确保你的错误处理代码能够正常工作,并能够处理各种不同的错误情况。
  • 考虑使用错误跟踪工具。 错误跟踪工具可以帮助你监控和分析错误,并更快地找到和解决问题。例如 Sentry, Bugsnag 等。
  • 保持代码简洁和可读性。 复杂的错误处理逻辑可能会使代码难以阅读和理解。尽量保持代码简洁和可读性,并使用注释来解释复杂的逻辑。

总结:拥抱异常,掌控代码

async/await 的异常处理是编写健壮的 JavaScript 代码的关键。通过结合使用 try-catch 块和 Promise.prototype.catch() 方法,你可以确保你的代码能够优雅地处理各种错误情况,并避免程序崩溃。 记住,拥抱异常,掌控代码! 祝各位老铁编码愉快!

希望今天的讲座对大家有所帮助。如果有什么问题,欢迎随时提问。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注