Appearance
异步与网络请求
JavaScript 里很多操作不是立刻完成的——setTimeout 要等几秒、fetch 要等服务器响应、读文件要等磁盘。这些"要等的操作"叫异步操作。同步代码是一行做完才做下一行,异步代码发出去之后不傻等,先去做别的,等结果回来再处理。
这篇覆盖两块:先弄懂 Promise 和 async/await 怎么处理"要等的操作",再用 fetch 调远程 API——fetch 就是前端跟后端通信的方式,后端的接口就是给 fetch 调的。
一、同步和异步
先看同步代码——一行做完才做下一行:
js
console.log("第一步");
console.log("第二步");
console.log("第三步");
// 输出顺序:第一步 → 第二步 → 第三步再看异步——setTimeout 设定 2 秒后执行,但 JS 不会干等 2 秒:
js
console.log("第一步");
setTimeout(() => console.log("第二步(2秒后)"), 2000);
console.log("第三步");
// 输出顺序:第一步 → 第三步 → (2秒后)第二步setTimeout 注册了回调就返回了,JS 继续执行第三步。2 秒到了,回调才执行。"第二步"被丢到队列里,不阻塞后面代码——这就是异步。
二、Promise——异步操作的结果占位符
setTimeout 用回调函数处理结果,操作多了就层层嵌套——回调里套回调,代码越来越深,排查也痛苦。Promise 是另一种处理异步的方式:它代表一个"现在还没出结果,但将来会有"的值。
js
// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("操作成功"); // 成功时调用 resolve,传结果
} else {
reject("操作失败"); // 失败时调用 reject,传错误
}
});resolve 和 reject 是两个函数——成功调 resolve,失败调 reject。
用 .then() 和 .catch() 接收结果:
js
promise
.then((result) => {
console.log(result); // 操作成功
})
.catch((error) => {
console.log(error); // 操作失败(成功时不会走到这)
});.then 接收 resolve 传的结果,.catch 接收 reject 传的错误。链式调用可以串起多个步骤:
js
fetchArticle(1)
.then((article) => {
console.log(article.title);
return fetchComments(article.id); // 返回新的 Promise
})
.then((comments) => {
console.log(comments);
})
.catch((error) => {
console.log("出错了:", error); // 任何一步出错都会走到这
});每个 .then 返回的值会传给下一个 .then。返回新的 Promise 时,下一个 .then 会等它完成。任何一步出错,都会跳到 .catch。
三、async/await——用同步的写法写异步
.then() 链长了之后缩进和可读性都不理想。async/await 是 Promise 的语法糖——让异步代码看起来像同步代码,更好读更好写。
js
async function loadArticle(id) {
try {
const article = await fetchArticle(id); // await 等 Promise 完成
console.log(article.title);
const comments = await fetchComments(article.id);
console.log(comments);
} catch (error) {
console.log("出错了:", error); // 跟 .catch 一样,任何 await 出错都走到这
}
}
loadArticle(1);几个要点:
async写在函数声明前面,表示这个函数里有异步操作await只能在async函数里用,它等 Promise 完成再继续往下try/catch替代.catch(),跟同步代码的错误处理写法一致await后面跟一个 Promise,拿到的是resolve传的值
async/await 和 .then() 做的事完全一样,只是写法不同。新代码优先用 async/await,可读性明显更好。
四、fetch——请求后端 API
fetch 是浏览器内置的 HTTP 请求函数。它返回一个 Promise,天然配合 async/await。前端调后端接口,基本都是用 fetch。
1 GET 请求
js
async function loadArticles() {
try {
const response = await fetch("http://localhost:8000/api/articles");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const articles = await response.json(); // 把响应体解析成 JSON
console.log(articles);
} catch (error) {
console.log("请求失败:", error);
}
}
loadArticles();fetch 返回的不是数据本身,是一个 Response 对象。要拿数据得调 response.json(),它把响应体从 JSON 文本解析成 JS 对象——这一步也是异步的,所以也要 await。
response.ok 判断 HTTP 状态码是不是 2xx(200-299)。不是 2xx 时 fetch 不会自动抛异常(只有网络不通才抛),要自己判断 response.ok 然后手动 throw。
2 POST 请求
提交数据用 POST,要传 method、headers 和 body:
js
async function createArticle(data) {
const response = await fetch("http://localhost:8000/api/articles", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data), // JS 对象转成 JSON 字符串
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
createArticle({ title: "新文章", content: "正文内容" })
.then((result) => console.log("创建成功", result))
.catch((error) => console.log("创建失败", error));JSON.stringify 把 JS 对象转成 JSON 字符串——fetch 的 body 只接受字符串。Content-Type: application/json 告诉后端"我发的是 JSON 格式"。
3 带认证 Token
需要登录的接口通常要求在请求头里带 Token:
js
async function fetchWithAuth(url) {
const token = localStorage.getItem("access_token"); // 从浏览器存储读 Token
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`, // Token 放在 Authorization 头
},
});
if (response.status === 401) {
// 401 说明 Token 过期或没带,跳登录页
console.log("未登录或 Token 过期");
return null;
}
return response.json();
}localStorage 是浏览器提供的本地存储——登录成功后把 Token 存进去,后续请求从里面取。Bearer ${token} 是最常见的 Token 传递方式,后端会从 Authorization 头里解析 Token。
五、封装请求函数
每次写 fetch 都要处理 response.ok、response.json()、错误处理,重复且容易漏。封装成一个函数,所有页面共用:
js
const BASE_URL = "http://localhost:8000/api";
async function request(path, options = {}) {
const token = localStorage.getItem("access_token");
const response = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.detail || `HTTP ${response.status}`);
}
return response.json();
}用起来就简单了:
js
// GET
const articles = await request("/articles");
// POST
const result = await request("/articles", {
method: "POST",
body: JSON.stringify({ title: "新文章" }),
});
// DELETE
await request("/articles/1", { method: "DELETE" });这个封装在后端基础篇的项目实战里会直接用——前端调用 request("/articles"),后端提供 /api/articles 接口,两边就这样连上了。
六、跨域和 CORS
浏览器有个安全限制叫同源策略:JS 默认只能请求跟当前页面同协议、同域名、同端口的地址。前端跑在 localhost:5173,后端跑在 localhost:8000,端口不一样就算跨域,浏览器会拦住请求。
js
// 前端在 localhost:5173
fetch("http://localhost:8000/api/articles");
// 浏览器控制台报错:CORS policy: No 'Access-Control-Allow-Origin'这个限制是浏览器加的,不是网络层的问题——请求其实发出去了,后端也响应了,但浏览器发现后端没说"我允许你跨域",就把响应拦住了。
解决方法在后端:FastAPI 里配置 CORS 中间件,告诉浏览器"我允许这些来源访问"。这个在后端进阶的 CORS 篇里会详细配。
跨域只在浏览器里有——用 curl、Python requests、Node.js 发请求都不会遇到 CORS。这是前端开发特有的"坑",知道根因在后端配置就行。