目录
- 前言
- 一、为什么需要异步?
- 二、回调函数
- 三、Promise
-
- [3.1 什么是 Promise?](#3.1 什么是 Promise?)
- [3.2 Promise 的状态](#3.2 Promise 的状态)
- [3.3 创建 Promise](#3.3 创建 Promise)
- [3.4 链式调用](#3.4 链式调用)
- [3.5 错误处理](#3.5 错误处理)
- [3.6 静态方法](#3.6 静态方法)
- 四、async/await
-
- [4.1 基本语法](#4.1 基本语法)
- [4.2 错误处理](#4.2 错误处理)
- [4.3 并行执行](#4.3 并行执行)
- [五、Fetch API](#五、Fetch API)
-
- [5.1 基本用法](#5.1 基本用法)
- [5.2 处理响应](#5.2 处理响应)
- [5.3 发送数据](#5.3 发送数据)
- [5.4 错误处理](#5.4 错误处理)
- 六、跨域问题
-
- [6.1 什么是跨域?](#6.1 什么是跨域?)
- [6.2 解决方案](#6.2 解决方案)
- 七、实战示例
- 八、总结
前言
在浏览器中,网络请求、定时器、文件读取等操作都是"耗时的"。如果同步等待,页面会卡死。
JavaScript 采用异步编程模型:发起请求后立即继续执行,等结果就绪再处理回调。
一、为什么需要异步?
同步 vs 异步:
js
// 同步:按顺序执行,前一步完成才执行下一步
console.log('开始');
const result = heavyTask(); // 假设耗时 3 秒
console.log('结果:', result);
console.log('结束');
// 执行顺序:开始 → (等待 3 秒) → 结果 → 结束
js
// 异步:发起后立即继续,不等待结果
console.log('开始');
setTimeout(() => {
console.log('3秒后');
}, 3000);
console.log('结束');
// 执行顺序:开始 → 结束 → (3秒后) → 3秒后
为什么 JS 需要异步?
- JS 是单线程的,同一时间只能做一件事
- 网络请求可能很慢,同步等待会阻塞页面
- 异步让 UI 保持响应,用户体验更好
二、回调函数
传统异步编程使用回调函数:
js
// 定时器
setTimeout(function() {
console.log('1秒后');
}, 1000);
// 事件绑定
btn.addEventListener('click', function() {
console.log('点击了');
});
问题:回调地狱(Callback Hell)
当多个异步操作有依赖关系时:
js
// 回调地狱示例
getUserInfo(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
getComments(detail.id, function(comments) {
console.log(comments);
});
});
});
});
层层嵌套,难以阅读和维护。
三、Promise
3.1 什么是 Promise?
Promise 是 ES6 引入的异步编程解决方案,用链式调用替代回调嵌套:
js
// Promise 版本
getUserInfo(userId)
.then(user => getOrders(user.id))
.then(orders => getOrderDetail(orders[0].id))
.then(detail => getComments(detail.id))
.then(comments => console.log(comments))
.catch(err => console.error(err));
3.2 Promise 的状态
Promise 有三种状态:
| 状态 | 含义 | 特点 |
|---|---|---|
pending |
进行中 | 初始状态 |
fulfilled |
已成功 | 不可变 |
rejected |
已失败 | 不可变 |
状态只能改变一次:pending → fulfilled 或 pending → rejected。
3.3 创建 Promise
js
// 创建 Promise
const promise = new Promise(function(resolve, reject) {
// 异步操作
setTimeout(function() {
const success = true;
if (success) {
resolve('成功的结果'); // 状态变为 fulfilled
} else {
reject(new Error('失败的原因')); // 状态变为 rejected
}
}, 1000);
});
// 使用 Promise
promise
.then(result => {
console.log('成功:', result);
})
.catch(error => {
console.log('失败:', error);
});
后端类比 :Promise 类似于 Java 的 CompletableFuture,表示一个未来会完成的操作。
3.4 链式调用
then 返回一个新的 Promise,可以继续链式调用:
js
fetch('/api/user')
.then(response => response.json()) // 返回 Promise
.then(user => {
console.log(user);
return fetch(`/api/orders?userId=${user.id}`); // 返回 Promise
})
.then(response => response.json())
.then(orders => console.log(orders));
3.5 错误处理
js
// 方式一:catch 捕获链中所有错误
fetch('/api/user')
.then(response => response.json())
.then(user => {
if (!user) {
throw new Error('用户不存在');
}
return user;
})
.catch(error => {
console.error('出错了:', error);
});
// 方式二:then 的第二个参数
promise.then(
result => console.log(result),
error => console.error(error)
);
推荐使用
catch:代码更清晰,捕获所有错误。
3.6 静态方法
js
// Promise.resolve():直接返回成功状态的 Promise
Promise.resolve(42).then(val => console.log(val)); // 42
// Promise.reject():直接返回失败状态的 Promise
Promise.reject(new Error('失败')).catch(err => console.log(err));
// Promise.all():所有都成功才成功,一个失败就失败
Promise.all([
fetch('/api/user'),
fetch('/api/orders')
])
.then(([userRes, ordersRes]) => {
// 两个请求都成功
})
.catch(err => {
// 任一请求失败
});
// Promise.allSettled():等待所有完成(无论成功失败)
Promise.allSettled([
fetch('/api/user'),
fetch('/api/orders')
])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
// Promise.race():返回最先完成的(无论成功失败)
Promise.race([
fetch('/api/slow'),
fetch('/api/fast')
])
.then(response => {
// 最先返回的请求
});
// Promise.any():返回第一个成功的,全部失败才失败
Promise.any([
fetch('/api/backup1'),
fetch('/api/backup2')
])
.then(response => {
// 第一个成功的请求
})
.catch(errors => {
// 全部失败
});
| 方法 | 作用 |
|---|---|
all() |
全部成功才成功 |
allSettled() |
等待全部完成 |
race() |
返回最快完成的 |
any() |
返回第一个成功的 |
四、async/await
4.1 基本语法
async/await 是 ES2017 引入的语法糖,让异步代码看起来像同步代码:
js
// async 函数返回 Promise
async function getUser() {
return '张三'; // 相当于 return Promise.resolve('张三')
}
getUser().then(name => console.log(name));
// await 等待 Promise 完成
async function fetchUser() {
const response = await fetch('/api/user'); // 等待请求完成
const user = await response.json(); // 等待解析完成
console.log(user);
return user;
}
fetchUser();
对比 Promise 链式调用:
js
// Promise 方式
function fetchUser() {
return fetch('/api/user')
.then(response => response.json())
.then(user => {
console.log(user);
return user;
});
}
// async/await 方式(更直观)
async function fetchUser() {
const response = await fetch('/api/user');
const user = await response.json();
console.log(user);
return user;
}
4.2 错误处理
使用 try...catch 处理错误:
js
async function fetchUser() {
try {
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('请求失败:', error);
throw error; // 可以选择继续抛出
}
}
4.3 并行执行
await 是顺序执行的,多个独立请求会串行:
js
// 串行执行(慢)
async function fetchAll() {
const user = await fetch('/api/user').then(r => r.json());
const orders = await fetch('/api/orders').then(r => r.json());
return { user, orders };
}
要并行执行,用 Promise.all:
js
// 并行执行(快)
async function fetchAll() {
const [user, orders] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/orders').then(r => r.json())
]);
return { user, orders };
}
五、Fetch API
Fetch 是浏览器提供的现代网络请求 API,基于 Promise。
5.1 基本用法
js
// GET 请求(默认)
fetch('/api/user')
.then(response => response.json())
.then(user => console.log(user));
// async/await 方式
async function getUser() {
const response = await fetch('/api/user');
const user = await response.json();
console.log(user);
}
5.2 处理响应
js
const response = await fetch('/api/user');
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
// 不同格式解析
const json = await response.json(); // JSON
const text = await response.text(); // 文本
const blob = await response.blob(); // 二进制(文件)
const arrayBuffer = await response.arrayBuffer(); // ArrayBuffer
// 获取响应头
console.log(response.headers.get('Content-Type'));
console.log(response.status); // 状态码
console.log(response.ok); // 是否成功(200-299)
5.3 发送数据
js
// POST 请求
async function createUser(userData) {
const response = await fetch('/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
return response.json();
}
// 使用示例
createUser({ name: '张三', age: 25 })
.then(user => console.log('创建成功:', user));
// 发送 FormData(文件上传)
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData // 浏览器自动设置 Content-Type
});
return response.json();
}
完整配置选项:
js
fetch('/api/user', {
method: 'POST', // 请求方法
headers: { // 请求头
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify(data), // 请求体
mode: 'cors', // 跨域模式
credentials: 'include', // 是否携带 Cookie
cache: 'no-cache', // 缓存策略
signal: controller.signal // 用于取消请求
});
5.4 错误处理
Fetch 只在网络错误时 reject,HTTP 错误状态(如 404、500)不会触发 catch:
js
fetch('/api/user/999')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.catch(error => {
// 只捕获网络错误
// 不捕获 HTTP 错误(需要在上面手动抛出)
});
推荐封装:
js
async function request(url, options = {}) {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.text();
throw new Error(`HTTP ${response.status}: ${error}`);
}
return response.json();
}
// 使用
try {
const user = await request('/api/user');
console.log(user);
} catch (error) {
console.error('请求失败:', error);
}
六、跨域问题
6.1 什么是跨域?
浏览器的同源策略限制:协议 + 域名 + 端口必须完全相同。
当前页面:https://example.com
允许访问:
✅ https://example.com/api/user(同源)
禁止访问:
❌ http://example.com/api/user(协议不同)
❌ https://api.example.com/user(域名不同)
❌ https://example.com:8080/api/user(端口不同)
6.2 解决方案
方案一:CORS(Cross-Origin Resource Sharing)
服务端设置响应头:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Spring Boot 示例:
java
@CrossOrigin(origins = "https://example.com")
@RestController
public class UserController {
// ...
}
方案二:代理服务器
前端请求同源代理服务器,代理服务器请求目标服务器:
浏览器 → 代理服务器(同源) → 目标服务器
开发环境常用 webpack 或 vite 配置代理:
js
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://target-server.com',
changeOrigin: true
}
}
}
}
方案三:JSONP(已过时)
利用 <script> 标签不受同源限制的特性。只支持 GET 请求,不推荐使用。
七、实战示例
示例:用户登录并获取信息
js
async function login(username, password) {
try {
// 登录
const loginRes = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
if (!loginRes.ok) {
const msg = await loginRes.text();
throw new Error(`登录失败:${msg}`);
}
const { token } = await loginRes.json();
// 获取用户信息
const userRes = await fetch('/api/user/info', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!userRes.ok) {
throw new Error('获取用户信息失败');
}
const user = await userRes.json();
console.log('登录成功:', user);
return user;
} catch (error) {
console.error(error);
throw error;
}
}
// 使用
login('admin', '123456').catch(err => {
alert('登录失败,请重试');
});
八、总结
| 类别 | 内容 | 要点 |
|---|---|---|
| 异步概念 | 为什么需要异步 | 单线程、不阻塞 UI |
| 回调函数 | 传统方式 | 回调地狱问题 |
| Promise | 异步编程核心 | 三种状态、链式调用、静态方法 |
| async/await | 语法糖 | 同步写法、try-catch 处理错误 |
| Fetch API | 网络请求 | 基于 Promise、注意 HTTP 错误处理 |
| 跨域 | 同源策略 | CORS、代理 |
核心记忆点:
- Promise :链式调用解决回调地狱,
all并行执行 - async/await :让异步代码像同步一样,用
try...catch处理错误 - Fetch :
response.ok判断成功,HTTP 错误不会触发 catch - 跨域:开发用代理,生产用 CORS
本文是"前端三基石"系列第二篇的续篇。结合前文的 DOM 操作,你已经掌握了 JavaScript 在浏览器中的核心能力:操作页面、响应用户、与服务器通信。