JavaScript 的异步编程之理解 Promise 和 理清 async/await 过程
在 JavaScript 中,异步编程 是一种 处理任务 的方式,它允许程序在 执行某个 耗时操作(如 网络请求、读取文件 等)时,不会阻塞 后续代码的 执行。
常见 异步编程 的 实现方式:
1 回调函数(Callbacks)
回调函数 是 异步编程 最基本的 方式。当一个 异步操作 完成时,会 调用 预先定义好的 函数。
例如,在浏览器中使用 setTimeout
函数,它接受 两个参数,第一个是 一个 回调函数,第二个是 延迟的 毫秒数。
// 示例代码 1
setTimeout(() => {
console.log("延迟后的操作");
}, 1000);
console.log("这行代码会立即执行");
在这个例子中,setTimeout
函数 会在 延迟 1000 毫秒后 执行 回调函数 中的代码。而 console.log("这行代码会立即执行");
会 立即执行,不会等待 setTimeout
中的 回调函数 执行完毕。
2 Promise
Promise 是一种 更现代的 异步处理方式,它代表了一个 异步操作 还未完成 但最终会 返回结果(成功或失败) 的操作。
Promise 有 三种状态:pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。可以通过 回调 .then()
和 .catch()
来 处理 成功 或 失败 的结果。
// 示例代码 2
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data received');
}, 1000);
});
}
fetchData()
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(error);
});
3 async/await + try/catch
async
和 await
是基于 Promise 的 更简洁的 异步编程 语法糖,使得 异步代码 写起来 更像 同步代码。
await
用于 等待 Promise 完成 并 返回结果,async
用于 标记 一个函数 为 异步函数,自动返回一个 Promise。
// 示例代码 3
async function getData() {
try {
let response = await fetch('https://example.com/api/data');
let data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
getData();
在这个例子中,使用 async
标记 异步函数 fetchData
,在 异步函数 中,await
关键字 会暂停 异步函数 内 代码的执行,即 当执行到 await fetch(...)
和 await response.json()
时,异步函数 fetchData
的执行会暂停,直到 Promise 已完成(成功或失败),
当 getData()
函数内部 try{...}
块中 await
修饰的 异步函数 返回的 Promise 状态为失败,或其他代码操作异常,会被内部的 catch{}
块捕获(getData
函数 返回的 Promise 状态为 fulfilled
)。但如果 catch{}
块中也出现异常,会使得 getData
函数 返回的 Promise 状态为 rejected
,回调 catch(error)
被调用。
异步编程 的主要优点是 避免了阻塞,使得 应用程序 在等待 外部资源(如 网络请求 或 文件操作)时 可以继续 执行 其他任务。
使用 async/await 优点
- 同步代码风格,
async
和await
组合使用 可以让 异步代码 在形式上更接近 同步代码。当我们使用纯 Promise 时,通过.then()
和.catch()
链式调用 的方式处理 异步操作,对于复杂的 异步流程,代码会变得 嵌套很深,难以阅读 和 维护。
// 示例代码 4
// 链式调用,嵌套很深
function getData() {
return fetch('https://example.com/api/data')
.then(response => response.json())
.then(data => {
// 假设这里又需要进行另一个异步操作
return new Promise((resolve, reject) => {
setTimeout(() => {
// 对数据进行一些修改
let modifiedData = data.map(item => item * 2);
resolve(modifiedData);
}, 1000);
});
})
.catch(error => console.error(error));
}
getData();
// 同步代码风格
async function getData() {
try {
let response = await fetch('https://example.com/api/data');
let data = await response.json();
await new Promise((resolve) => {
setTimeout(() => {
let modifiedData = data.map(item => item * 2);
resolve(modifiedData);
}, 1000);
});
return data;
} catch (error) {
console.error(error);
}
}
getData();
- 集中式错误处理:在
async
函数中,可以使用try/catch
语句块来 捕获 异步操作中 出现的 错误。这与 同步代码的错误处理方式类似,更加直观。相比之下,使用 Promise 时,每个.then()
和.catch()
都需要 单独考虑 错误处理。
// 示例代码 5
async function getData() {
try {
let response = await fetch('https://example.com/api/data');
let data = await response.json();
// 假设这里可能出现运行时错误,比如数据格式不符合预期
if (!Array.isArray(data)) {
throw new Error('Data is not in the expected format');
}
return data;
} catch (error) {
console.error(error);
}
}
getData();
- 顺序执行和依赖关系清晰:
await
关键字 可以确保 异步操作 按照 顺序执行。当一个 异步操作的结果 依赖于 另一个异步操作的完成时,await
能够很好地处理这种 依赖关系。
// 示例代码 6
async function getFirstData() {
let response = await fetch('https://example.com/api/firstData');
return await response.json();
}
async function getSecondData() {
let firstData = await getFirstData();
let response = await fetch('https://example.com/api/secondData?param=' + firstData.id);
return await response.json();
}
getSecondData();