大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
异步 Promise 与 async/await
这一课你将能做到
- 解释为什么网络请求不能「写在下一行就当成已经有结果」
- 用
async/await+try/catch写一次取数流程 - 给界面加上「加载中 / 成功 / 失败」三种状态中的至少两种
为什么要学
fetch 去服务器拿数据需要时间。
若 JS 死等,页面就卡死——所以用异步:先继续干别的,结果以后再处理。
人话:你点了外卖(发起请求),先干别的事;外卖到了(Promise 完成)再吃饭(更新页面)。
本站前端调后端 API、任何真实产品拉列表,都建立在异步之上。
先建立图像
Promise(承诺)三种状态:
pending 进行中
fulfilled 成功,得到值
rejected 失败,得到错误
async 函数里:
await 某个 Promise → 等到结束再往下走
(看起来像同步,仍不卡死浏览器主线程)
点击「加载」
→ 显示「加载中」
→ await fetch(...)
→ 成功:显示数据 / 失败:显示错误
跟着做
步骤 1:认识延迟与 await
function wait(ms) {
// 返回一个「ms 毫秒后完成」的 Promise
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
async function demo() {
console.log("开始");
await wait(500);
console.log("0.5 秒后");
}
demo();
console.log("demo() 已调用,但这行可能先打印");
你应看到:demo() 已调用… 往往出现在「0.5 秒后」之前——说明异步不会堵住后面的同步代码。
步骤 2:忘记 await 会怎样(常见 bug)
async function f() {
const p = wait(100); // 没 await
console.log(p); // Promise 对象,不是「结束之后的值」
const done = await wait(100);
console.log("await 之后:", done); // undefined(因 resolve 没传值)
}
f();
新手最常见问题:把 Promise 当成最终数据。看见 Promise { <pending> } 就要怀疑漏了 await。
步骤 3:假接口 + 界面三种状态
下面用公开测试接口;若网络不通可改成 await wait(500) 自己返回假数据。
保存为 js-async.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>异步</title>
</head>
<body>
<button type="button" id="load">加载探测文本</button>
<p id="out">尚未加载</p>
<script>
const out = document.getElementById("out");
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function loadPoem() {
out.textContent = "加载中…";
try {
const res = await fetch("https://www.cloudflare.com/cdn-cgi/trace");
if (!res.ok) throw new Error("HTTP " + res.status);
const text = await res.text();
out.textContent = text.slice(0, 120) + "…";
} catch (err) {
out.textContent = "失败:" + err.message;
console.error(err);
}
}
document.getElementById("load").addEventListener("click", function () {
loadPoem();
});
// 离线演练:把 loadPoem 换成下面这段即可
async function loadFake() {
out.textContent = "加载中…";
try {
await wait(500);
out.textContent = "假数据:加载成功";
} catch (err) {
out.textContent = "失败:" + err.message;
}
}
</script>
</body>
</html>
点击后应先见「加载中…」,再变为成功文本或失败信息。
步骤 4:then/catch 对照(能读即可)
wait(200)
.then(function () {
console.log("完成");
})
.catch(function (err) {
console.error(err);
});
async/await 是建立在 Promise 上的语法糖,读旧代码时能认 then 即可;新代码优先 async/await + try/catch。
步骤 5:串行与并行直觉
async function serial() {
const a = await wait(200);
const b = await wait(200); // 总共约 400ms
console.log("串行结束");
}
async function parallel() {
// 两个一起开始,再一起等
await Promise.all([wait(200), wait(200)]); // 约 200ms
console.log("并行结束");
}
serial();
parallel();
互相独立的请求用 Promise.all 往往更快;有依赖(第二个需要第一个的结果)才串行 await。
步骤 6:按钮防重复点击
let loading = false;
document.getElementById("load").addEventListener("click", async function () {
if (loading) return;
loading = true;
this.disabled = true;
try {
await loadPoem();
} finally {
loading = false;
this.disabled = false;
}
});
异步进行中重复点击会叠请求。用 loading 标志或禁用按钮,是产品级细节的起点。
讲清楚要点
- 因为网络有延迟,所以用 Promise / async 表达「稍后才有结果」。
- 因为
await只能写在async函数里(或特定顶层模块环境),所以入口常包一层async function。 - 因为失败也会发生,所以用
try/catch(或.catch),并给用户可读提示。 - 因为等待时用户需要反馈,所以UI 至少要有加载态与失败态。
- 因为漏写
await会拿到 Promise,所以看见奇怪对象先检查 await。 - 因为独立请求可并行,所以合适时用
Promise.all,别无脑一路串行。
你可能会懵的地方
-
异步是多线程吗?
浏览器里 JS 主要仍单线程;靠事件循环安排「稍后继续」。先建立「稍后处理」直觉即可。 -
为什么有时顺序乱?
没 await 的异步会交叉。需要顺序就 await;要并行再Promise.all。 -
CORS 报错
浏览器安全策略拦跨域。学前端必遇;本课先保证你理解 await 流程,接口可换假数据。 -
res.json()也要 await?
要。fetch完成只代表响应头大致可用;读 body(json/text)仍是异步。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 不写 await | 拿到 Promise | await 或 then |
| 无加载态 | 用户以为卡死 | 「加载中」文案 |
| 空 catch | 吞掉错误 | 至少 console + 用户提示 |
忽略 res.ok | 404 当成功解析 | 非 ok 抛错或分支处理 |
| 连点发多数请求 | 乱序回写界面 | loading 锁 / 禁用按钮 |
自测三问
- 用外卖比喻解释 Promise 的三种状态。
- 忘记 await 的典型现象是什么?
- 为什么异步流程也要 try/catch,并且界面要有加载态?
小结一句话
异步让等待网络时页面不卡死;用 async/await 写清顺序,处理加载与失败,并分清串行与并行。
延伸
- MDN:使用 Promise
- MDN:async 函数
- 下一课:模块与 npm——代码如何拆文件。