各位靓仔靓女,咱们今天聊聊 JavaScript 里 await
和 try-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
块捕获。
第三幕:await
和 try-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/await
和 try/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 请求可能发生的错误,并返回 null
。processMultipleData
函数会遍历所有 URL,并调用 fetchData
函数获取数据。如果某个 API 请求失败,fetchData
函数会返回 null
,processMultipleData
函数会忽略这个结果,继续处理下一个 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
的状态(fulfilled
或 rejected
),以及一个 value
属性(如果 Promise
resolve)或一个 reason
属性(如果 Promise
reject)。
我们可以根据 status
属性来判断 Promise
是否成功,并分别处理成功和失败的结果。
第八幕:总结与展望
await
和 try-catch
是处理 JavaScript 异步错误的利器。它们可以让你像写同步代码一样处理异步操作,并捕获异步操作可能发生的错误。但是,错误处理是一门艺术,需要根据具体的场景选择合适的处理方式。希望今天的讲座能帮助你更好地掌握 JavaScript 异步错误处理的技巧。
记住,错误处理不是可有可无的,它是保证代码健壮性和可靠性的重要手段。写出健壮的代码,才能让你少加班,多摸鱼,生活更美好!
感谢各位的观看,下课!