JavaScript 异步编程完全指南 - 小白也能懂
一、核心概念:Promise 是什么?
1.1 生活化比喻
// Promise 就像外卖订单
const orderPromise = 点外卖("红烧肉");
// 这时候你拿到的是"订单号",不是"红烧肉"
console.log(orderPromise); // "订单号:12345"
// 只有等外卖送到(await),才能吃到红烧肉
const food = await orderPromise; // 等待外卖送达
console.log(food); // "香喷喷的红烧肉"
1.2 Promise 的三种状态
// 1. 等待中 (pending) - 外卖已下单,正在制作
const promise = new Promise((resolve, reject) => {
// 这时候状态是 pending
});
// 2. 已完成 (fulfilled) - 外卖送达
resolve("数据获取成功"); // 状态变为 fulfilled
// 3. 已拒绝 (rejected) - 外卖取消或失败
reject("网络错误"); // 状态变为 rejected
二、async/await:让异步变简单
2.1 基本用法
// 传统 Promise 写法(复杂)
function getData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error(error);
});
}
// async/await 写法(简单直观)
async function getData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error(error);
}
}
2.2 async 函数的特点
// async 函数永远返回 Promise
async function hello() {
return "Hello World";
}
// 等价于
function hello() {
return Promise.resolve("Hello World");
}
// 使用方式
hello().then(result => console.log(result)); // "Hello World"
// 或者
const result = await hello(); // "Hello World"
三、重点难点详解
3.1 await 到底在等什么?
// ❌ 常见误解:await 是在"阻塞"代码
// ✅ 正确理解:await 是在"等待Promise兑现"
async function example() {
console.log("开始点外卖");
// 不是"阻塞",而是"登记回调,等外卖到了再继续"
const food = await 点外卖();
console.log("吃到外卖了"); // 这行代码要等外卖到了才执行
}
3.2 为什么 Promise.all 需要 await?
// 情景:同时点三份外卖
async function 点大餐() {
// ❌ 错误写法:不要外卖单,直接就要吃
const [饭, 菜, 汤] = Promise.all([
点米饭(), 点红烧肉(), 点番茄汤()
]);
// 结果:饭、菜、汤 都是 undefined!
// ✅ 正确写法:等所有外卖都到了再分配
const [饭, 菜, 汤] = await Promise.all([
点米饭(), 点红烧肉(), 点番茄汤()
]);
// 结果:真的能吃到饭、菜、汤!
}
3.3 错误处理对比
// Promise 的错误处理
function getData() {
return fetch('/api')
.then(response => response.json())
.catch(error => {
console.log('错误了:', error);
});
}
// async/await 的错误处理
async function getData() {
try {
const response = await fetch('/api');
const data = await response.json();
return data;
} catch (error) {
console.log('错误了:', error);
}
}
四、实际应用场景
4.1 场景1:用户注册流程
async function 用户注册(用户信息) {
try {
// 1. 验证用户信息
const 验证结果 = await 验证用户信息(用户信息);
// 2. 检查用户名是否重复
const 用户名可用 = await 检查用户名(用户信息.用户名);
// 3. 创建用户账号
const 用户ID = await 创建用户(用户信息);
// 4. 发送欢迎邮件(不阻塞主流程)
发送欢迎邮件(用户ID).catch(错误 => {
console.log('邮件发送失败,但不影响注册:', 错误);
});
return { 成功: true, 用户ID };
} catch (错误) {
console.log('注册失败:', 错误);
return { 成功: false, 错误信息: 错误.message };
}
}
4.2 场景2:商品详情页数据加载
async function 加载商品页(商品ID) {
try {
// 并行加载所有数据(同时点多个外卖)
const [商品信息, 用户评论, 推荐商品, 库存状态] = await Promise.all([
获取商品信息(商品ID),
获取商品评论(商品ID),
获取推荐商品(商品ID),
检查库存(商品ID)
]);
// 所有数据都到了,渲染页面
渲染页面({
商品: 商品信息,
评论: 用户评论,
推荐: 推荐商品,
库存: 库存状态
});
} catch (错误) {
// 任何一个请求失败都会到这里
console.log('页面加载失败:', 错误);
显示错误页面();
}
}
五、常见坑点及解决方案
5.1 坑点1:在循环中错误使用 await
// ❌ 错误:一个个等,太慢了
async function 慢速操作() {
for (const id of [1, 2, 3, 4, 5]) {
const 数据 = await 获取数据(id); // 等完第一个再等第二个...
}
}
// ✅ 正确:一起等,速度快
async function 快速操作() {
const 所有数据 = await Promise.all([
获取数据(1), 获取数据(2), 获取数据(3), 获取数据(4), 获取数据(5)
]);
}
5.2 坑点2:忘记处理错误
// ❌ 危险:错误会默默消失
async function 危险函数() {
const 数据 = await 获取数据(); // 如果出错,程序会崩溃!
}
// ✅ 安全:妥善处理错误
async function 安全函数() {
try {
const 数据 = await 获取数据();
return 数据;
} catch (错误) {
console.log('出错了,但程序不会崩溃:', 错误);
return 默认数据;
}
}
5.3 坑点3:在非 async 函数中使用 await
// ❌ 错误:普通函数不能用 await
function 普通函数() {
const 数据 = await 获取数据(); // SyntaxError!
}
// ✅ 正确:只有 async 函数能用 await
async function 异步函数() {
const 数据 = await 获取数据(); // 正确
}
六、性能优化技巧
6.1 并行 vs 串行
// 串行:总共需要 3秒
async function 串行请求() {
const 用户 = await 获取用户(1); // 等1秒
const 订单 = await 获取订单(用户.id); // 再等1秒
const 地址 = await 获取地址(用户.id); // 再等1秒
}
// 并行:总共只需要 1秒
async function 并行请求() {
const [用户, 订单, 地址] = await Promise.all([
获取用户(1), // 同时开始
获取订单(1), // 同时开始
获取地址(1) // 同时开始
]);
// 总共只等最慢的那个(1秒)
}
6.2 错误处理优化
// 方法1:传统 try-catch
async function 方法1() {
try {
const 数据 = await 不可靠的API();
return 数据;
} catch {
return 默认值;
}
}
// 方法2:使用 .catch()
async function 方法2() {
const 数据 = await 不可靠的API().catch(() => 默认值);
return 数据;
}
七、记忆口诀
7.1 核心口诀
见到 Promise,就要想 await
没有 await,数据就不在
多个请求并行发,Promise.all 来打包
错误处理不能忘,try-catch 保平安
7.2 使用流程
1. 函数前加 async
2. Promise 前加 await
3. 多个请求用 Promise.all
4. 错误处理用 try-catch
5. 返回结果自动包装成 Promise
八、实战检查清单
下次写异步代码时,问自己:
- 函数前面加 async 了吗?
- Promise 前面加 await 了吗?
- 多个请求用 Promise.all 优化了吗?
- 错误处理写了吗?
- 返回的是实际数据还是 Promise?
掌握了这些,你就真正理解 JavaScript 异步编程了!
不是的! resolve
和 reject
不是自定义函数,而是 JavaScript 引擎提供的参数。
它们是 Promise 构造函数的参数
new Promise((resolve, reject) => {
// ↑ ↑ ↑
// 这些是 JavaScript 引擎自动传入的函数
// 我们只是在这里接收和使用它们
});
实际工作原理
// JavaScript 引擎内部大致是这样处理的:
class Promise {
constructor(executor) {
// 引擎预先定义好的函数
const resolve = (value) => { /* 处理成功逻辑 */ };
const reject = (reason) => { /* 处理失败逻辑 */ };
// 把这两个函数作为参数传给我们写的函数
executor(resolve, reject);
}
}
// 我们使用时:
new Promise((我们自己起的resolve参数名, 我们自己起的reject参数名) => {
// 这里可以任意命名,但通常叫 resolve/reject
我们自己起的resolve参数名("成功!");
我们自己起的reject参数名("失败!");
});
参数名可以自定义(但不推荐)
// 虽然可以自定义名字,但不要这样做(容易混淆)
new Promise((成功, 失败) => {
if (Math.random() > 0.5) {
成功("我们在一起吧!💖");
} else {
失败("你是个好人😭");
}
})
.then(result => console.log(result))
.catch(error => console.log(error));
正确的理解方式
// JavaScript 引擎说:"我给你两个函数,你用它们来告诉我结果"
new Promise((引擎给的resolve函数, 引擎给的reject函数) => {
// 我们的业务逻辑
const 表白成功 = true;
if (表白成功) {
引擎给的resolve函数("💖 接受表白"); // 告诉引擎:成功了
} else {
引擎给的reject函数("😭 拒绝表白"); // 告诉引擎:失败了
}
});
更清晰的比喻
想象你去餐厅点餐:
// 餐厅(JavaScript引擎)给你一个呼叫器
const 点餐Promise = new Promise((上菜, 退款) => {
// 厨房处理中...
setTimeout(() => {
if (有食材) {
上菜("您的菜品"); // 按下"上菜"按钮
} else {
退款("食材不足"); // 按下"退款"按钮
}
}, 3000);
});
// 你等待结果
点餐Promise
.then(菜品 => console.log("收到:", 菜品))
.catch(原因 => console.log("退款原因:", 原因));
总结
resolve
和reject
不是我们定义的- 它们是 JavaScript 引擎创建并传递给我们的
- 我们只是 使用 这两个函数来告诉 Promise 最终的结果
- 参数名可以任意,但约定俗成叫
resolve
和reject
所以你不是在定义函数,而是在 使用 JavaScript 提供给你的通信工具!