异步 JavaScript
回调、Promise、async/await,以及不阻塞地从 API 获取数据。
你将学到
- JavaScript 为何是单线程的,事件循环做什么
- 回调的问题(回调地狱)
- Promise:then、catch、finally 与链式调用
- async/await 写出线性、可读的异步代码
- 用 fetch() 从 API 获取 JSON
概念讲解
事件循环
JavaScript 是单线程的——一次只执行一个任务。为避免慢操作冻结页面,事件循环把耗时工作(网络请求、定时器)交给浏览器的后台 API,等结果就绪后再运行回调。
这意味着异步代码不会从上到下执行。理解这一点是避免竞态条件和 bug 的关键。
回调(旧方式)
回调是稍后被调用的函数:
setTimeout(() => {
console.log("runs after 1 second");
}, 1000);
嵌套多个回调会形成回调地狱——深度缩进、难以阅读的代码:
getUser(id, (user) => {
getOrders(user, (orders) => {
getOrderItems(orders[0], (items) => {
// ... 继续嵌套
});
});
});
Promise
Promise 表示一个将来可用的值。它可以是 pending(待定)、fulfilled(已完成)或 rejected(已拒绝)。
fetch("https://api.example.com/data")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Failed:", error))
.finally(() => console.log("Done"));
Promise 可以扁平地链式调用,错误会传播到下一个 catch,比嵌套回调清晰得多。
async / await
async/await(ES2017)让你写出看起来像同步的异步代码。async 函数总是返回 Promise,await 会暂停直到 Promise 完成:
async function loadData() {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Failed:", error);
}
}
这是现代标准。尽可能用它替代 then 链。
获取数据
fetch() 是发起 HTTP 请求的内置 API。它返回一个 Promise,resolve 为 Response 对象:
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ada" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const users = await res.json();
务必检查 res.ok——fetch 只在网络错误时 reject,HTTP 404 或 500 不会。
并行运行 Promise
// 顺序——慢
const a = await fetchA();
const b = await fetchB();
// 并行——快
const [aResult, bResult] = await Promise.all([fetchA(), fetchB()]);
Promise.all 并发运行多个 Promise 并等待全部完成。用它加速独立请求。
示例代码
// async/await 配合 fetch 和错误处理
async function getUser(userId) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("Failed to fetch user:", error.message);
return null;
}
}
// 并行运行多个请求
async function loadDashboard() {
const [user1, user2, user3] = await Promise.all([
getUser(1),
getUser(2),
getUser(3),
]);
const users = [user1, user2, user3].filter(Boolean);
users.forEach((u) => console.log(`${u.name} — ${u.email}`));
}
// 自己创建一个简单的 Promise
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function countdown() {
for (let i = 3; i > 0; i--) {
console.log(i);
await delay(1000);
}
console.log("Go!");
}
loadDashboard();
countdown();
展示了带错误处理的真实 fetch 调用、用 Promise.all 并行请求、从零创建 Promise(delay),以及用 await 配合定时器构建倒计时。
动手试一试
- HTTP Request Tester
发送 GET/POST 请求并检查响应——正是 fetch 做的事。
- WebSocket Client Tester
通过 WebSocket 的异步通信——另一种异步模式。
常见错误
错误写法
忘记 await,得到 Promise 对象而非值
正确做法
await 会暂停直到 Promise 完成。没有它你只会得到 Promise 对象本身。在 async 函数中需要已完成的值时,务必使用 await。
错误写法
fetch 后不检查 response.ok
正确做法
fetch 只在网络错误时 reject。HTTP 404 或 500 仍然 resolve。调用 response.json() 前务必检查 if (!response.ok) throw new Error(...)。
相关资源
相关工具
- Server-Sent Events Tester
现代 Web 应用中另一种异步流模式。
- HTTP Status Codes
你的 fetch 调用会返回的状态码参考。
相关速查表
- JavaScript Cheatsheet
Promise、async/await 与 fetch 参考。
练习 异步 JavaScript
2 个练习
查阅不认识的术语
新手友好的术语表用大白话解释编程术语——变量、函数、DOM、Promise 等等。
动手做项目
通过引导式项目应用所学,含起始代码与解决方案。
解读错误信息
用大白话解释常见错误——什么意思、为什么发生、如何修复。