异步编程与网络请求
异步编程与网络请求
JavaScript 是单线程的:一个主线程执行所有代码。如果网络请求是”同步”的,发请求时页面会整个卡死直到响应返回。所以 JS 用异步机制处理耗时操作(网络请求、定时器、文件读取)。
这一篇讲异步编程的核心(回调 → Promise → async/await)和网络请求(fetch / AJAX),是 Web API 阶段最核心、也最容易拉开差距的一篇。
一、同步 vs 异步
1 | // 同步:顺序执行,遇到耗时操作就阻塞 |
事件循环(简要)
- JS 有执行栈(同步代码)和任务队列(异步回调)。
- 同步代码先全部执行完,异步回调进入任务队列,等主线程空闲后依次执行。
- 宏任务:
setTimeout、setInterval、I/O、事件;微任务:Promise.then、async/await。 - 每轮事件循环:先执行完微任务,再执行下一个宏任务。
记忆点:同步代码先跑完,异步回调后执行。这就是为什么
setTimeout(fn, 0)也不是”立刻”执行。
二、回调函数与回调地狱
早期异步靠回调函数实现,但层层嵌套导致”回调地狱”:
1 | getData(function (a) { |
问题:可读性差、难维护、错误处理困难。于是有了 Promise。
三、Promise
1. 三种状态
pending(进行中)→fulfilled(成功)/rejected(失败)- 状态一旦改变就不可逆,只能从 pending 变一次。
2. 基本用法
1 | const p = new Promise((resolve, reject) => { |
3. 链式调用
then 返回一个新 Promise,可以一直链下去:
1 | fetch("/user") |
4. Promise.all / race
1 | Promise.all([p1, p2, p3]).then(results => { |
Java 对照:
Promise≈CompletableFuture(thenApply/thenCompose对应then,allOf/anyOf对应all/race)。
四、async / await
async/await 是 Promise 的语法糖,让异步代码像同步一样写。
1 | async function getData() { |
错误处理用 try/catch:
1 | async function getData() { |
⚠️ 易错点:
async函数必须await才能拿到值,直接调用返回的是 Promise 对象。await只能用在async函数内部。
五、网络请求 fetch
fetch 是现代的 Promise 风格请求 API。
1. GET 请求(默认)
1 | fetch("https://api.example.com/users") |
2. POST 请求
1 | fetch("https://api.example.com/users", { |
3. 用 async/await 改写
1 | async function getUsers() { |
4. 响应对象常用属性/方法
| 属性/方法 | 说明 |
|---|---|
res.json() |
解析为 JSON 对象(返回 Promise) |
res.text() |
解析为文本 |
res.status |
状态码(200、404、500…) |
res.ok |
是否成功(状态码 200~299) |
⚠️ fetch 最大的坑:HTTP 错误状态(404、500)不会自动 reject,
fetch只会在网络故障时 reject。所以必须手动检查res.ok:
1 | fetch(url) |
六、XMLHttpRequest(经典 AJAX,简要)
XMLHttpRequest(XHR)是 AJAX 的经典实现,回调风格,写法繁琐,现已被 fetch 逐步取代:
1 | const xhr = new XMLHttpRequest(); |
对照:
fetch是 Promise 风格(简洁、可链式),XHR 是回调风格(繁琐)。现代项目基本用fetch或axios。
七、跨域 CORS
1. 同源策略
浏览器安全机制:默认只允许同源(协议 + 域名 + 端口三者完全相同)的请求访问数据。
- 同源:
https://a.com/page请求https://a.com/api✅ - 跨域:
https://a.com请求https://b.com/api❌(协议/域名/端口任一不同)
2. 解决跨域的常见思路
| 方案 | 说明 |
|---|---|
| CORS | 后端设置响应头 Access-Control-Allow-Origin,主流方案 |
| 代理 | 开发时用 devServer 代理,生产用 Nginx 反向代理 |
| JSONP | 利用 <script> 标签跨域,只支持 GET,已很少用 |
1 | // 后端 CORS 响应头(示意) |
记忆点:跨域是浏览器的限制(服务器之间互相请求没有跨域问题),所以”代理”方案能绕过——请求先到同源服务器,再由服务器转发。
八、速查表
| 需求 | 写法 |
|---|---|
| 创建 Promise | new Promise((resolve, reject) => {}) |
| 成功/失败回调 | p.then(onOk).catch(onErr) |
| 全部完成 | Promise.all([p1, p2]) |
| 竞速 | Promise.race([p1, p2]) |
| 定义 async 函数 | async function f() {} |
| 等待结果 | const x = await promise |
| 异步错误捕获 | try { await ... } catch (err) {} |
| GET 请求 | fetch(url).then(res => res.json()) |
| POST 请求 | fetch(url, { method: "POST", body: JSON.stringify(...) }) |
| 检查是否成功 | if (!res.ok) throw new Error(...) |
九、复习要点
- JS 是单线程,耗时操作靠异步;同步代码先执行完,异步回调后执行(事件循环)。
- 回调地狱 → Promise → async/await 是异步的三代演进。
- Promise 三状态(pending/fulfilled/rejected),状态不可逆;
then返回新 Promise 可链式。 Promise.all全部成功才执行,race谁先完成用谁。- async 函数返回 Promise,必须 await 才能拿值;
await只能在 async 函数内。 fetch返回 Promise,用res.json()解析;**HTTP 错误不会自动 reject,要检查res.ok**。- POST 要设
method、headers、body(对象要JSON.stringify)。 - 跨域是同源策略(浏览器限制),解决靠 CORS / 代理 / JSONP。