JS `await` 关键字在 `try-catch` 中的异步错误捕获

各位靓仔靓女,咱们今天聊聊 JavaScript 里 awaittry-catch 这对欢喜冤家,看看它们在异步错误处理中是怎么配合演出的。

开场白:异步的坑,同步的盾

都知道 JavaScript 是单线程的,为了不让 UI 卡死,异步操作那是家常便饭。但异步一多,错误处理就成了大问题。传统的 try-catch 只能捕获同步代码的错误,对异步操作就有点力不从心了。这时候,await 关键字就带着它的好基友 try-catch 来拯救世界了。

第一幕:try-catch 的同步局限

先来回顾一下 try-catch 的基本用法:

try {
  // 可能会抛出错误的代码
  console.log("开始执行...");
  throw new Error("哎呀,出错了!");
  console.log("这行代码不会执行");
} catch (error) {
  // 捕获错误并处理
  console.error("捕获到错误:", error.message);
} finally {
  // 无论是否发生错误,都会执行
  console.log("代码执行结束。");
}

这段代码很好理解,try 块里的代码一旦抛出错误,就会立即跳转到 catch 块执行。finally 块里的代码无论是否发生错误都会执行。

但是,如果 try 块里执行的是异步操作呢?

try {
  console.log("开始执行异步操作...");
  setTimeout(() => {
    throw new Error("异步操作出错了!");
  }, 1000);
  console.log("这行代码会执行,因为异步操作还没执行");
} catch (error) {
  console.error("捕获到错误:", error.message); // 不会执行到这里
} finally {
  console.log("代码执行结束。"); // 会执行
}

运行这段代码,你会发现 catch 块根本没有执行!这是因为 setTimeout 里的错误是在 try-catch 块之外抛出的,try-catch 管不着啊! setTimeout 属于异步操作,try-catch 只能捕获同步代码块内的错误。

第二幕:await 的登场,同步异步一线牵

await 关键字就是来解决这个问题的。它可以让你像写同步代码一样处理异步操作。它只能在 async 函数中使用。

async function myFunction() {
  try {
    console.log("开始执行异步操作...");
    const result = await new Promise((resolve, reject) => {
      setTimeout(() => {
        // 模拟成功
        // resolve("异步操作成功!");
        // 模拟失败
        reject(new Error("异步操作出错了!"));
      }, 1000);
    });
    console.log("异步操作结果:", result); // 如果 reject,这行不会执行
  } catch (error) {
    console.error("捕获到错误:", error.message); // 会执行
  } finally {
    console.log("代码执行结束。"); // 会执行
  }
}

myFunction();

这段代码中,await 关键字会暂停 myFunction 函数的执行,直到 Promise 对象 new Promise resolve 或 reject。如果 Promise resolve,await 会返回 resolve 的值;如果 Promise reject,await 会抛出一个错误,这个错误会被 try-catch 块捕获。

第三幕:awaittry-catch 的完美配合

有了 await,我们就可以把异步操作放在 try 块里,用 catch 块来捕获异步操作的错误。

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("Error fetching data:", error.message);
    // 可以选择重新抛出错误,或者返回一个默认值
    throw error; // 重新抛出错误
    // return null; // 返回一个默认值
  }
}

async function processData() {
  try {
    const data = await fetchData("https://jsonplaceholder.typicode.com/todos/1");
    console.log("Data:", data);
  } catch (error) {
    console.error("Error processing data:", error.message);
  }
}

processData();

在这个例子中,fetchData 函数使用 await 来等待 fetch 请求完成。如果 fetch 请求失败(例如网络错误、服务器返回错误状态码),await 会抛出一个错误,这个错误会被 catch 块捕获。catch 块可以选择处理这个错误,例如打印错误信息、重新抛出错误,或者返回一个默认值。

processData 函数调用 fetchData 函数,并使用 try-catch 块来捕获 fetchData 函数可能抛出的错误。

第四幕:错误处理的最佳实践

  • 不要过度使用 try-catch 只在你知道可能会发生错误的地方使用 try-catch
  • catch 块里做有意义的事情。 不要只是简单地打印错误信息,应该根据错误类型采取相应的措施,例如重试、回退、通知用户等等。
  • 考虑重新抛出错误。 如果 catch 块无法完全处理错误,可以考虑重新抛出错误,让上层调用者来处理。
  • 使用 finally 块来执行清理操作。 无论是否发生错误,finally 块里的代码都会执行,可以用来释放资源、关闭连接等等。
  • 分层处理错误。 可以在不同的层级上处理错误。例如,数据访问层可以处理网络错误,业务逻辑层可以处理业务规则错误,UI 层可以处理用户界面错误。
  • 使用日志记录错误。 记录错误信息可以帮助你诊断问题。

第五幕:错误处理的场景分析

咱们来分析几个常见的错误处理场景:

场景 错误类型 处理方式
网络请求失败 TypeError: Failed to fetch、HTTP 错误 重试请求、显示错误信息、提供离线模式
JSON 解析错误 SyntaxError: Unexpected token ... 检查 API 返回的数据是否符合 JSON 格式、使用默认值
用户输入错误 验证错误 显示错误信息、阻止提交、提供输入提示
服务器内部错误 HTTP 500 错误 显示错误信息、记录错误日志、通知管理员
数据库连接错误 Error: connect ECONNREFUSED ... 重试连接、使用备用数据库、显示错误信息
文件读取错误 Error: ENOENT: no such file or directory 检查文件是否存在、使用默认文件、显示错误信息
权限错误 HTTP 403 错误 显示错误信息、引导用户登录、检查用户权限
超时错误 Error: Timeout 增加超时时间、重试请求、显示错误信息
内存溢出(在 Node.js 中) Error: JavaScript heap out of memory 增加内存限制、优化代码、减少内存占用

第六幕:案例分析:使用 async/awaittry/catch 处理多个异步操作

假设我们需要从多个 API 获取数据,并进行一些处理。如果其中一个 API 请求失败,我们希望继续处理其他 API 的数据,而不是立即停止整个流程。

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(`Error fetching data from ${url}:`, error.message);
    return null; // 返回 null,表示该 API 请求失败
  }
}

async function processMultipleData() {
  const urls = [
    "https://jsonplaceholder.typicode.com/todos/1",
    "https://jsonplaceholder.typicode.com/posts/1",
    "https://this-url-does-not-exist.com/data", // 故意制造一个错误
    "https://jsonplaceholder.typicode.com/users/1",
  ];

  const results = [];

  for (const url of urls) {
    const data = await fetchData(url);
    if (data) {
      results.push(data);
    }
  }

  console.log("Successfully fetched data:", results);
}

processMultipleData();

在这个例子中,fetchData 函数会捕获每个 API 请求可能发生的错误,并返回 nullprocessMultipleData 函数会遍历所有 URL,并调用 fetchData 函数获取数据。如果某个 API 请求失败,fetchData 函数会返回 nullprocessMultipleData 函数会忽略这个结果,继续处理下一个 URL。

这样,即使其中一个 API 请求失败,我们仍然可以获取其他 API 的数据。

第七幕:更优雅的错误处理方式:Promise.allSettled

如果我们需要等待所有异步操作完成,无论它们是否成功,可以使用 Promise.allSettled 方法。

async function processMultipleDataWithAllSettled() {
  const urls = [
    "https://jsonplaceholder.typicode.com/todos/1",
    "https://jsonplaceholder.typicode.com/posts/1",
    "https://this-url-does-not-exist.com/data", // 故意制造一个错误
    "https://jsonplaceholder.typicode.com/users/1",
  ];

  const promises = urls.map(url =>
    fetchData(url)
      .then(data => ({ status: 'fulfilled', value: data }))
      .catch(error => ({ status: 'rejected', reason: error }))
  );

  const results = await Promise.allSettled(promises);

  console.log("Results:", results);

  const successfulResults = results
    .filter(result => result.status === 'fulfilled')
    .map(result => result.value);

  const failedResults = results
    .filter(result => result.status === 'rejected')
    .map(result => result.reason);

  console.log("Successful results:", successfulResults);
  console.log("Failed results:", failedResults);
}

processMultipleDataWithAllSettled();

在这个例子中,我们使用 Promise.allSettled 方法来等待所有 fetchData 函数完成。Promise.allSettled 方法会返回一个数组,包含每个 Promise 的结果。每个结果对象都有一个 status 属性,表示 Promise 的状态(fulfilledrejected),以及一个 value 属性(如果 Promise resolve)或一个 reason 属性(如果 Promise reject)。

我们可以根据 status 属性来判断 Promise 是否成功,并分别处理成功和失败的结果。

第八幕:总结与展望

awaittry-catch 是处理 JavaScript 异步错误的利器。它们可以让你像写同步代码一样处理异步操作,并捕获异步操作可能发生的错误。但是,错误处理是一门艺术,需要根据具体的场景选择合适的处理方式。希望今天的讲座能帮助你更好地掌握 JavaScript 异步错误处理的技巧。

记住,错误处理不是可有可无的,它是保证代码健壮性和可靠性的重要手段。写出健壮的代码,才能让你少加班,多摸鱼,生活更美好!

感谢各位的观看,下课!

发表回复

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