异步与 fetch · 前端:HTML、CSS 与 JavaScript
网页怎么加载数据又不卡住?
课程目录为什么需要它
加载用户资料或者一串消息,都要向服务器发请求然后等,有时一等就是几秒。如果网页在等的时候停住了,什么都点不了。JavaScript 的办法是异步代码:发出请求,继续做别的,等答案来了再处理。所有现代应用都建立在它之上。
直觉理解
任何需要时间的操作都会给你一个 Promise:一张「以后给你值」的欠条。
const res = await fetch("https://api.example.com/user/1");
const user = await res.json();- **
fetch(url)** 发出一个 HTTP 请求,返回一个「响应」的 Promise。 - **
await让这个函数暂停,直到 Promise 有结果,但不会卡住整个页面。它只能用在async function** 里面。 - 状态码在 200–299 时
res.ok为 true;res.status是状态码数字。fetch 遇到 404 或 500 不会抛出错误,所以要检查res.ok。 res.json()也是一个 Promise:同样要 await。- 用 **
try { … } catch (err) { … }** 包起来,处理网络故障。
顺序很重要:先跑普通代码,再跑 Promise 的回调,最后才是定时器。
本课程里的 fetch 连的是一个模拟 API,所以你的代码每次拿到的答案都一样。
定义
Promise
An object standing for a value that isn't ready yet. It ends up fulfilled (with a value) or rejected (with an error).代表一个还没准备好的值的对象。最后要么兑现(得到值),要么被拒绝(得到错误)。
async / await
async marks a function that can pause; await pauses it until a Promise settles and gives you the value.async 标记一个可以暂停的函数;await 让它暂停到 Promise 有结果,然后把值交给你。
fetch
The browser's function for HTTP requests. It resolves with a response for any status; check res.ok yourself.浏览器发 HTTP 请求的函数。不管状态码是多少,它都会给出一个响应;res.ok 要你自己检查。
示例
边等边做别的
在互动课里运行setTimeout 把工作安排到以后;下一行马上就执行。
console.log("1: start");
setTimeout(() => console.log("3: timer done"), 1000);
console.log("2: carrying on");1: start 2: carrying on 3: timer done
- L2安排一个函数在 1 秒后运行,然后继续往下走。
- L3比定时器先运行,虽然它在代码里写得更靠后。
获取一个用户
在互动课里运行await 响应,再 await 它的 JSON。
async function loadUser(id) {
const res = await fetch(`https://api.example.com/user/${id}`);
const user = await res.json();
return user.name;
}
loadUser(1).then(name => console.log("Hello,", name));
console.log("waiting...");waiting... Hello, Ada
- L2请求发出去了;这个函数在这里暂停。
- L3响应体是 JSON;res.json() 解析它(它也是异步的)。
- L7调用 async 函数会返回一个 Promise;完成时 .then 里的函数会运行。
- L8这一行先打印:没有任何东西在等 loadUser。
服务器说「不」的时候
在互动课里运行404 也是一个响应。要检查 res.ok。
async function loadUser(id) {
const res = await fetch(`https://api.example.com/user/${id}`);
if (!res.ok) {
return `Error ${res.status}`;
}
const user = await res.json();
return user.name;
}
async function main() {
console.log(await loadUser(2));
console.log(await loadUser(99));
}
main();Linus Error 404
- L3404 时 res.ok 是 false,所以在这里带着一条消息停下。
- L11main 里的 await 依次等待每个结果。
同时发两个请求
在互动课里运行Promise.all 并行地等待多个 Promise。
async function nameOf(id) {
const res = await fetch(`https://api.example.com/user/${id}`);
const user = await res.json();
return user.name;
}
async function main() {
const names = await Promise.all([nameOf(1), nameOf(2)]);
console.log(names);
}
main();[ 'Ada', 'Linus' ]
- L8两个请求同时开始;await 等到两个都有答案。
常见错误
忘了写 await
不写 await,拿到的是 Promise 而不是值:会出现 res.json is not a function,或者输出 Promise { <pending> }。
不检查 res.ok
只有网络故障时 fetch 才会拒绝。404 或 500 都会作为普通响应返回,响应体往往是错误信息,而不是你要的数据。
一个一个地等
await a(); await b(); 花的时间是两者之和。如果它们互不依赖,await Promise.all([a(), b()]) 只用花较慢那个的时间。
小测验
1. 这段代码会输出什么?
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");查看答案
答案:A
先跑普通代码(A、D),再跑 Promise 回调(C),最后才是定时器(B),哪怕是 0 毫秒的定时器。
2. 服务器返回 404 Not Found。`await fetch(url)` 会怎样?
查看答案
答案:A
fetch 只在网络出问题时才失败。HTTP 错误也是正常的响应:要检查 res.ok。
动手练习
3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。
- 预测输出事件的顺序
按顺序准确写出它打印的内容。
- 改需求漏掉的 await
getName(id)应该返回用户的名字,现在却报错了。修好它。 - 从零写加载个人资料
写一个
async function loadProfile(id),请求https://api.example.com/user/<id>,返回:- 请求成功时,返回名字和城市,比如
"Ada (London)" - 响应不是 ok(比如 404)时,返回
"User not found" - fetch 本身失败时,返回
"Network error"(用 try/catch)
- 请求成功时,返回名字和城市,比如
本课术语
| 术语 | 中文 | 英文定义 |
|---|---|---|
| Promise | Promise | A value that will be ready later, or an error. |
| async / await | async / await | Write asynchronous code that reads top to bottom. |
| fetch | fetch | The browser function for HTTP requests. |