各位观众老爷,大家好!我是你们的老朋友,今天咱们聊聊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
值的数组。如果数组中任何一个Promise
reject
,新的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
,并将其作为第一个参数返回。如果Promise
resolve
,第一个参数为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
的本质,才能更好地运用它,写出高质量的代码。
感谢各位的观看! 咱们下次再见!