核心概念 JavaScript 是单线程语言,但通过异步编程实现非阻塞操作。主要包括:
- Promise - 异步操作的标准化解决方案
- async/await - Promise 的语法糖
- 事件循环 - JavaScript 的执行机制
一、Promise
1.1 为什么需要 Promise?
回调地狱问题 异步任务嵌套多层会导致代码难以维护:
// ❌ 回调地狱
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
// 无限嵌套...
});
});
});
});ES6 引入 Promise 对象,提供优雅的异步编程解决方案。
1.2 Promise 的三种状态
状态说明
- pending(等待):初始状态,既没有成功也没有失败
- fulfilled(已成功):操作成功完成
- rejected(已失败):操作失败
stateDiagram-v2 [*] --> pending: 创建 Promise pending --> fulfilled: resolve() pending --> rejected: reject() fulfilled --> [*] rejected --> [*]
状态特点
- 状态不可逆,一旦改变就不会再变
resolve和reject只执行一个
1.3 基本用法
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
const success = true;
if (success) {
resolve("成功的数据"); // 将状态改为 fulfilled
} else {
reject("失败的原因"); // 将状态改为 rejected
}
});
// 使用 Promise
promise
.then((data) => {
console.log(data); // "成功的数据"
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log("无论成功失败都会执行");
});1.4 Promise 静态方法
| 方法 | 说明 | 返回值 |
|---|---|---|
Promise.resolve() | 创建一个已解决的 Promise | resolved Promise |
Promise.reject() | 创建一个已拒绝的 Promise | rejected Promise |
Promise.all() | 全部成功才成功,有一个失败就失败 | Promise |
Promise.allSettled() | 等待全部完成(不管成功失败) | Promise |
Promise.race() | 返回最先完成的结果 | Promise |
Promise.any() | 返回第一个成功的结果 | Promise |
Promise.all() - 全部成功
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then((values) => {
console.log(values); // [1, 2, 3]
});
// 有一个失败就返回失败
const p4 = Promise.reject("失败");
Promise.all([p1, p2, p4]).catch((error) => {
console.log(error); // "失败"
});Promise.race() - 赛跑
const p1 = new Promise((resolve) => setTimeout(() => resolve("A"), 100));
const p2 = new Promise((resolve) => setTimeout(() => resolve("B"), 200));
Promise.race([p1, p2]).then((value) => {
console.log(value); // "A"(最快完成的)
});Promise.allSettled() - 全部完成
const p1 = Promise.resolve(1);
const p2 = Promise.reject("失败");
const p3 = Promise.resolve(3);
Promise.allSettled([p1, p2, p3]).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: '失败' },
// { status: 'fulfilled', value: 3 }
// ]
});1.5 Promise 链式调用
fetchUser()
.then((user) => {
console.log("获取用户:", user);
return fetchPosts(user.id); // 返回新的 Promise
})
.then((posts) => {
console.log("获取文章:", posts);
return fetchComments(posts[0].id);
})
.then((comments) => {
console.log("获取评论:", comments);
})
.catch((error) => {
console.error("发生错误:", error);
});链式调用原理
then()方法返回一个新的 Promise- 可以通过
return值传递给下一个then()
1.6 Promise 常见问题
错误处理
// ✅ 推荐:使用 catch
promise
.then((data) => { })
.catch((error) => {
console.error(error);
});
// ⚠️ 注意:then 的第二个参数也可以捕获错误
promise.then(
(data) => { },
(error) => { } // 但无法捕获前面 then 中的错误
);
// ✅ 推荐:try/catch (async/await)
async function handle() {
try {
const data = await promise;
} catch (error) {
console.error(error);
}
}Promise 穿透
Promise.resolve(1)
.then(null) // 空的 then 会透传值
.then((value) => console.log(value)); // 1二、async/await
async/await 是 Promise 的语法糖
- async 函数:返回 Promise
- await 表达式:等待 Promise 完成
2.1 基本语法
// async 函数声明
async function fetchData() {
return "数据"; // 自动包装成 Promise.resolve("数据")
}
// 等同于
function fetchData() {
return Promise.resolve("数据");
}2.2 await 使用
async function getData() {
// 等待 Promise 完成
const data = await fetchUser();
// 等待另一个 Promise
const posts = await fetchPosts(data.id);
return posts;
}
// 使用
getData().then((posts) => {
console.log(posts);
});await 只能在 async 函数内使用
// ❌ 错误 function test() { await promise; // SyntaxError } // ✅ 正确 async function test() { await promise; }
2.3 错误处理
async function fetchData() {
try {
const data = await fetchUser();
console.log(data.message);
} catch (error) {
console.error('Error:', error.message);
} finally {
console.log('Finally block executed');
}
}2.4 并发处理
// ❌ 串行执行(慢)
async function serial() {
const a = await fetchA(); // 等待 1s
const b = await fetchB(); // 等待 1s
const c = await fetchC(); // 等待 1s
// 总共 3s
}
// ✅ 并行执行(快)
async function parallel() {
const [a, b, c] = await Promise.all([
fetchA(), // 1s
fetchB(), // 1s
fetchC() // 1s
]);
// 总共 1s
}
// ✅ 互不依赖的并发
async function independent() {
const p1 = fetchA();
const p2 = fetchB();
const a = await p1;
const b = await p2;
}2.5 async/await vs Promise
| 特性 | async/await | Promise |
|---|---|---|
| 可读性 | 高(同步写法) | 低(链式调用) |
| 错误处理 | try/catch | .catch() |
| 中间值处理 | 方便 | 需要在外层作用域 |
| 调试 | 同步调试 | 断点跳跃 |
推荐使用 async/await 代码更简洁,错误处理更直观
三、事件循环(Event Loop)
3.1 JavaScript 是单线程的
为什么需要事件循环? JavaScript 是单线程语言,同一时间只能做一件事。为了处理异步操作,使用事件循环机制。
3.2 任务分类
同步代码 vs 异步代码
// 同步代码:立即执行
console.log(1);
// 异步代码:稍后执行
setTimeout(() => console.log(2), 0);宏任务 vs 微任务
| 类型 | 说明 | 示例 |
|---|---|---|
| 宏任务 | 宿主环境(浏览器)发起的任务 | setTimeout、setInterval、UI 渲染、DOM 事件 |
| 微任务 | JavaScript 引擎发起的任务 | Promise.then、MutationObserver、queueMicrotask |
3.3 事件循环执行顺序
graph TD A[开始] --> B[执行同步代码] B --> C[同步代码执行完毕?] C -->|否| B C -->|是| D[执行微任务队列] D --> E[微任务队列为空?] E -->|否| D E -->|是| F[从宏任务队列取一个] F --> G[执行宏任务] G --> B
console.log("1. 开始");
setTimeout(() => {
console.log("2. 宏任务:setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("3. 微任务:Promise");
});
console.log("4. 结束");
// 输出顺序:
// 1. 开始(同步)
// 4. 结束(同步)
// 3. 微任务:Promise
// 2. 宏任务:setTimeout3.4 执行优先级
process.nextTick(Node.js) > Promise.then > setTimeout > setImmediate
注意事项
new Promise()中的代码是同步执行的then/catch回调是异步的requestAnimationFrame和定时器顺序不一定
3.5 经典面试题
setTimeout(() => console.log(1), 0);
new Promise((resolve) => {
console.log(2); // 同步代码
resolve();
console.log(3); // 同步代码
}).then(() => {
console.log(4); // 微任务
});
console.log(5); // 同步代码
// 输出顺序:2, 3, 5, 4, 1解析:
- 同步代码:
console.log(2)→console.log(3)→console.log(5) - 微任务队列:
then回调 - 宏任务队列:
setTimeout回调 - 清空微任务:
console.log(4) - 执行宏任务:
console.log(1)
四、最佳实践
异步编程建议
- 优先使用 async/await 而非 Promise 链式调用
- 并发请求使用
Promise.all()- 总是使用 try/catch 捕获错误
- 避免在循环中 await,使用
Promise.all()- 理解事件循环,避免阻塞主线程
// ✅ 推荐写法
async function fetchUsers() {
try {
const [users, posts] = await Promise.all([
fetch("/api/users"),
fetch("/api/posts")
]);
return { users, posts };
} catch (error) {
console.error("请求失败:", error);
throw error;
}
}