各位观众老爷,大家好!我是你们的老朋友,今天咱们聊聊JavaScript里那个甜甜的语法糖:Async/Await。
别看它名字挺高大上,其实就是Generator和Promise这对好基友的马甲,穿上之后立马变得人见人爱,花见花开。咱们今天就扒一扒这件马甲,看看里面藏着啥秘密。
开胃小菜:Promise 温故知新
在进入Async/Await的世界之前,咱们先简单回顾一下Promise。Promise这玩意儿,说白了就是一个代表异步操作最终结果的对象。它可以处于三种状态:
- Pending (进行中): 初始状态,还没完成,像你刚开始追妹子。
- Fulfilled (已成功): 操作成功完成,就像你成功抱得美人归。
- Rejected (已失败): 操作失败,就像你表白被拒,惨遭滑铁卢。
Promise最常用的两个方法是.then()和.catch()。.then()用于处理成功的情况,.catch()用于处理失败的情况。
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => { // 模拟异步请求
if (userId > 0) {
resolve({ id: userId, name: "张三", age: 25 }); // 成功
} else {
reject("用户ID无效"); // 失败
}
}, 1000);
});
}
fetchUserData(1)
.then(user => {
console.log("用户信息:", user);
})
.catch(error => {
console.error("出错了:", error);
});
fetchUserData(0)
.then(user => {
console.log("用户信息:", user); // 不会执行
})
.catch(error => {
console.error("出错了:", error); // 用户ID无效
});
上面这段代码,fetchUserData函数返回一个Promise,模拟了一个异步请求。如果userId大于0,Promise会resolve,否则会reject。.then()和.catch()分别处理成功和失败的情况。
正餐:Async/Await 闪亮登场
Async/Await是 ES2017 (ES8) 引入的,它让异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性。
async关键字: 用于声明一个异步函数。异步函数会默认返回一个Promise对象。await关键字: 只能在async函数中使用。它会暂停async函数的执行,直到await后面的Promise对象resolve或reject。
咱们用Async/Await改造一下上面的例子:
async function getUserData(userId) {
try {
const user = await fetchUserData(userId); // 等待Promise resolve
console.log("用户信息:", user);
return user; // 返回值会被包装成 Promise.resolve(user)
} catch (error) {
console.error("出错了:", error);
throw error; // 抛出的错误会被包装成 Promise.reject(error)
}
}
getUserData(1); // 调用异步函数
getUserData(0); // 调用异步函数, 会输出错误信息
这段代码看起来是不是更像同步代码了?await fetchUserData(userId) 会暂停 getUserData 函数的执行,直到 fetchUserData 返回的 Promise 对象 resolve。如果 Promise 对象 reject 了,try...catch 语句会捕获到这个错误。
Async/Await 的本质:Generator + Promise
Async/Await 其实是 Generator 和 Promise 的语法糖。 编译器会将 Async/Await 代码转换成使用 Generator 和 Promise 的代码。
咱们先简单了解一下 Generator。Generator 是一种特殊的函数,它可以暂停执行,并在稍后恢复执行。它使用 yield 关键字来暂停执行,并返回一个值。
function* myGenerator() {
console.log("第一次执行");
yield 1; // 暂停,返回 1
console.log("第二次执行");
yield 2; // 暂停,返回 2
console.log("第三次执行");
return 3; // 返回 3,并结束 Generator 函数
}
const gen = myGenerator(); // 返回一个 Generator 对象
console.log(gen.next()); // 第一次执行,输出 "第一次执行",返回 { value: 1, done: false }
console.log(gen.next()); // 第二次执行,输出 "第二次执行",返回 { value: 2, done: false }
console.log(gen.next()); // 第三次执行,输出 "第三次执行",返回 { value: 3, done: true }
console.log(gen.next()); // 继续执行,返回 { value: undefined, done: true }
Generator 函数使用 function* 声明,yield 关键字用于暂停执行并返回一个值。gen.next() 方法用于恢复执行,并返回一个包含 value 和 done 属性的对象。value 属性是 yield 返回的值,done 属性表示 Generator 函数是否执行完毕。
现在,咱们用 Generator 和 Promise 来模拟 Async/Await 的行为。
function asyncToGenerator(fn) {
return function () {
const gen = fn.apply(this, arguments); // 执行 Generator 函数,得到 Generator 对象
return new Promise((resolve, reject) => {
function step(key, arg) {
let generatorResult;
try {
generatorResult = gen[key](arg); // 执行 Generator 函数的 next/throw 方法
} catch (error) {
return reject(error); // 捕获 Generator 函数中的错误
}
const { value, done } = generatorResult;
if (done) {
return resolve(value); // Generator 函数执行完毕,resolve Promise
} else {
// 如果 value 是 Promise 对象,则等待 Promise 对象 resolve
Promise.resolve(value).then(
val => step("next", val), // 成功,继续执行 Generator 函数
err => step("throw", err) // 失败,抛出错误
);
}
}
step("next"); // 启动 Generator 函数
});
};
}
const getUserDataGenerator = asyncToGenerator(function* (userId) {
try {
const user = yield fetchUserData(userId); // yield 返回 Promise 对象
console.log("用户信息:", user);
return user;
} catch (error) {
console.error("出错了:", error);
throw error;
}
});
getUserDataGenerator(1);
getUserDataGenerator(0);
这个 asyncToGenerator 函数接受一个 Generator 函数作为参数,并返回一个新的函数。这个新的函数会执行 Generator 函数,并返回一个 Promise 对象。step 函数用于递归地执行 Generator 函数的 next 或 throw 方法,直到 Generator 函数执行完毕。
这段代码的逻辑和使用 Async/Await 的代码几乎一样,但是它使用了 Generator 和 Promise 来实现同样的功能。这就是 Async/Await 的本质:Generator + Promise。
Async/Await 的优势
- 代码更简洁易懂:
Async/Await让异步代码看起来更像同步代码,避免了Promise的回调地狱。 - 错误处理更方便: 可以使用
try...catch语句来捕获异步操作中的错误。 - 调试更简单: 可以使用调试器单步执行
Async/Await代码,就像调试同步代码一样。
Async/Await 的注意事项
await关键字只能在async函数中使用。async函数会默认返回一个Promise对象。await会暂停async函数的执行,直到await后面的Promise对象resolve或reject。- 可以使用
try...catch语句来捕获异步操作中的错误。 - 避免在循环中使用
await,这会导致性能问题。 尽量使用Promise.all并发执行。
Async/Await 的应用场景
Async/Await 可以用于各种异步操作,例如:
- 发起 HTTP 请求: 可以使用
fetch或axios等库来发起 HTTP 请求。 - 读取文件: 可以使用
fs模块来读取文件。 - 访问数据库: 可以使用
mysql或mongodb等库来访问数据库。 - 处理定时器: 可以使用
setTimeout或setInterval来处理定时器。
一些高级技巧
-
并发执行多个
Promise:Promise.all和Promise.allSettled如果你需要并发执行多个
Promise,可以使用Promise.all或Promise.allSettled。Promise.all: 接受一个Promise数组作为参数,返回一个新的Promise。只有当数组中所有的Promise都resolve时,新的Promise才会resolve,并返回一个包含所有Promise的resolve值的数组。如果数组中任何一个Promisereject,新的Promise就会立即reject。
async function fetchData() { const [user, posts] = await Promise.all([ fetchUserData(1), fetchUserPosts(1) ]); console.log("用户信息:", user); console.log("用户文章:", posts); }Promise.allSettled: 接受一个Promise数组作为参数,返回一个新的Promise。当数组中所有的Promise都完成(resolve或reject)时,新的Promise才会resolve,并返回一个包含所有Promise结果的数组。数组中的每个结果对象都包含status和value或reason属性。
async function fetchData() { const results = await Promise.allSettled([ fetchUserData(1), fetchUserPosts(0) // 假设这个请求会失败 ]); results.forEach(result => { if (result.status === "fulfilled") { console.log("成功:", result.value); } else { console.error("失败:", result.reason); } }); } -
处理
Async/Await中的错误:更优雅的错误处理除了使用
try...catch语句,还可以使用第三方库(例如async-await-error-handling)来更优雅地处理Async/Await中的错误。// 假设你已经安装了 async-await-error-handling 库 import { to } from 'async-await-error-handling'; async function fetchData() { const [error, user] = await to(fetchUserData(1)); if (error) { console.error("出错了:", error); return; } console.log("用户信息:", user); }to函数会捕获Promise的reject,并将其作为第一个参数返回。如果Promiseresolve,第一个参数为null。
对比表格
为了更清晰地对比 Promise 和 Async/Await,咱们来个表格:
| 特性 | Promise |
Async/Await |
|---|---|---|
| 语法 | .then() 和 .catch() 回调 |
async 和 await 关键字 |
| 可读性 | 相对复杂,容易形成回调地狱 | 更简洁易懂,更像同步代码 |
| 错误处理 | 需要在每个 .catch() 中处理错误 |
可以使用 try...catch 语句统一处理错误 |
| 本质 | 基于状态机的异步编程模型 | Generator 和 Promise 的语法糖 |
| 使用场景 | 适用于各种异步操作,但代码结构相对复杂 | 适用于各种异步操作,代码结构更清晰简洁 |
| 调试 | 调试相对困难,需要理解 Promise 的状态变化 |
调试更简单,可以像调试同步代码一样单步执行 |
总结
Async/Await 是 JavaScript 中一个非常强大的特性,它可以让异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性。虽然它本质上是 Generator 和 Promise 的语法糖,但它提供了一种更优雅、更简洁的异步编程方式。掌握 Async/Await,你就可以轻松驾驭 JavaScript 的异步世界,写出更加高效、更加易懂的代码。
今天的分享就到这里,希望对大家有所帮助!如果有什么问题,欢迎在评论区留言,咱们一起探讨。
最后,记住一点:技术是死的,人是活的。理解了 Async/Await 的本质,才能更好地运用它,写出高质量的代码。
感谢各位的观看! 咱们下次再见!