Promise对象
JS优雅解决异步操作的库:频繁的回调只会让代码可读性和调试难度变得更差。
Promise状态:pending(初始),fulfiled(成功),rejected(失败)
用法:
-
创建一个异步处理数据的函数(传递resolve,reject) 处理成功调用resolve(异步操作数据),失败调用reject(异步操作失败数据)
-
创建使用异步处理函数构建Promise对象
-
使用Promise对象
resolve和reject的参数就是处理成功失败的结果 1.promise.then(处理异步操作的结果的函数).catch(异步操作失败数据的函数) 2.promise.then(成功,失败) 当然还有finally:成功失败都执行
链式操作:多个异步调用顺序执行
promise.then(f1).then(f2).finally();
这里f1可以是返回新promise的函数,也可以不是
如果不是新promise:则相当于调用Promise.resolve(返回值)
过程中也可以用catch,失败会跳到最近的catch
Promise.resolve(value):返回一个已成功的promise对象
Promise.reject(value):返回一个失败的promise对象
==>可以将promise.then(f1)中的f1看作是一个返回Promise.resolve或者Promise.reject的函数
其他用法
var p = Promise.all([p1,p2,p3]); 生成一个promise数组,只有对应数组的promise都成功,p才成功,成功返回值作为数组传递给p,首个失败的返回值会传递给p
var p = Promise.race([p1,p2,p3]); 数组中有一个成功就行,首个成功的返回值传递给p
async和 await:让Promise更简洁,可以通过try/catch统一捕获错误
- async :声明一个异步函数,直接返回promise对象
- await :等待 Promise 完成(只能在 async 函数内使用!)后面函数不一定要是一个promise,不是他会包装成
Promise.resolve(值)
async function loadUser() {
const user = await fetch('/api/user/1').then(r => r.json());
console.log(user.name);
return user;
}
loadUser().then(result => console.log(result));
axios库
基于 Promise 的 HTTP 客户端
- 项目里面不会直接用axios请求: 工具包内统一封装好axios请求,配置请求/响应拦截器,埋点 统一的封装api模块
应用
创建一个axios对象然后使用其方法,也可以直接快捷使用axios完成请求的发送
import axios from 'axios';
// 方式1:直接使用 axios
axios.get('/test1');
// 方式2:创建实例默认实例
axios.create().get('/test2');
// 方式3:自定义使用,直接发送请求
axios({
url: '/user',
method: 'get',
baseURL: 'https://api.example.com',
header:{"":""}, // 请求头
// ... 其他配置
});
// 没有配置baseURL就会用当前域名
语法
axios.get(url, config)
axios.post(url, data, config)
// data 请求体数据
// config : 配置请求头,get请求的参数params,post的data
一个简单config对象
var config={
header:{"":""}, // 请求头
baseURL:"http://localhost:8080",
timeout: 15000
}
拦截器配置
统一使用一个axios对象,为这个axios配置配置拦截器,也可以配置全局拦截器。
import axios from 'axios';
// 全局拦截器(影响所有 axios.xxx 调用)
axios.interceptors.request.use(...);
axios.interceptors.response.use(...);
// 实例拦截器(只影响 service.xxx 调用)
const service = axios.create();
service.interceptors.request.use(...);
service.interceptors.response.use(...);
基本配置:统一的拦截,统一的baseURL,loading状态管理
import axios from 'axios';
const service = axios.create({
baseURL: , // 通过环境获取配置文件数据
timeout: 15000,
headers: { 'Content-Type': 'application/json' }
});
// 请求拦截器
service.interceptors.request.use(
config => {
const userStore = useUserStore();
const token = userStore.token || localStorage.getItem('token');
if (token) {
config.headers.Authorization = token;
}
// loading状态管理:发送请求的一个进度条,提高交互
NProgress.start(); // 需要安装NProgress
// 还可以开始埋点时间:记录前端一些重要操作
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
service.interceptors.response.use(
response => {
// 1. 隐藏 Loading
NProgress.done();
// 2. 业务状态码判断
const res = response.data; // 统一只返回业务数据
if (res.code !== 200) {
Message.error(res.message || '操作失败');
if (res.code === 401) {
router.push('/login');
}
return Promise.reject(res);
}
return res.data; // 返回业务数据,简化组件调用
},
error => {
// 1. 隐藏 Loading
NProgress.done();
// 3. 网络错误提示
let message = '请求失败';
if (error.response) {
switch (error.response.status) {
case 401:
message = '登录已过期';
router.push('/login'); // 跳转
break;
case 403:
message = '权限不足';
break;
case 500:
message = '服务器错误';
break;
default:
message = error.response.data?.message || '未知错误';
}
} else if (error.request) {
message = '网络连接失败';
}
Message.error(message);
return Promise.reject(error);
}
);
前端接口防重复
axios真正发送请求的dispatchRequest函数 axios拦截器链条本质是一个Promise 链,拦截器内返回config最终传递到dispathcRequest函数,如果不是config就会不会发送真正的请求,如果返回一个promise,则会等待这个promise执行完成,然后将结果传递给响应拦截器。基于这个原理,我检测到相同请求时,直接返回第一个请求的promise对象即可。
service.interceptors.request.use(
(config) => {
// 关键:如果带有内部标记,直接放行,走真实请求
if (config._isDedupRequest) {
return config; // dispatchRequest,发真实请求!
}
// 非防重请求直接放行
if (config.dedup === false) {
return config;
}
const key = generateReqKey(config); // 处理判断重复的逻辑
if (pendingRequests.has(key)) {
console.log('复用请求:', key);
// pendingRequests 存放promise
return pendingRequests.get(key)!; // 返回缓存的 Promise
}
// 创建新请求,但标记为“真实请求”,避免再次拦截
// 递归调用service(config)
const requestPromise = service({
...config,
_isDedupRequest: true, // 加标记!下次进入拦截器会直接 return config
}).finally(() => {
pendingRequests.delete(key);
});
pendingRequests.set(key, requestPromise);
return requestPromise; // 返回 Promise,Axios 会等待它
},
(error) => Promise.reject(error)
);
ajax
// 1. 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
// 2. 设置请求方式和 URL
xhr.open('GET', 'https://api.example.com/data', true); // true 表示异步
// 3. 设置响应处理函数
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// 请求完成且成功
console.log(xhr.responseText); // 获取响应文本
}
};
// 4. 发送请求
xhr.send();
ajax常用属性/方法:
- readyState :(0-4) 4表示请求完成
- status:http响应码 20x:200成功,204无返回内容 30x:301重定向 40x:客户端错误,404资源不存在 50x:服务器错误,500服务器内部错误,502网关错误
- responseText:响应内容,string类型
- responseXML : 用得少
- response:发送时设置好responseType时会自动解析response为对象,默认下和responseText一样为string
- responseType:responseType = 'json' json:将响应response转为对象,失败为null blob:文件,图片,二进制 arraybuffer:自定义二进制
- setRequestHeader(k,v):设置请求头
- send(string) : string会直接放在请求体里面(get应该会忽略),通常来说如果是json格式,需要设置请求头'Content-Type': 'application/json',否则后端框架可能会报错
- getResponseHeader(k) : 获取响应头
- getAllResponseHeaders() : 所有响应头
下载文件
var blob = xhr.response; // 获取 Blob 对象
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob); // 创建临时 URL
link.download = filename; // 设置下载文件名
link.click(); // 模拟点击,触发下载
- 通过a标签点击下载是流式下载,内存占用小
- 可以通过提交表单的形式,返回一个文本数据(校验/文件位置这些),后端返回设置响应头**
Content-Disposition: attachment**实现点击按钮,标签下载文件 - ajax下载:先下载到用户内存,然后再入盘,优点就是方便设置请求
上传文件
-
普通表单:input type="file" , 表单设置enctype="multipart/form-data" 流式上传
-
ajax : 通过js内置对象FormData生成一个可以提交的表单数据
function uploadFile() { const fileInput = document.getElementById('fileInput'); // 读取input选择的文件 const file = fileInput.files[0]; if (!file) return alert('请选择文件'); const formData = new FormData(); formData.append('file', file); formData.append('description', '用户上传的文件'); const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload'); // 不设置 请求头Content-Type!浏览器会自动设为 multipart/form-data // 进度监听 xhr.upload.onprogress = function(e) { if (e.lengthComputable) { const percent = Math.round((e.loaded / e.total) * 100); document.getElementById('progress').innerText = percent + '%'; } }; xhr.onload = function() { if (xhr.status === 200) { alert('上传成功!'); } else { alert('上传失败:' + xhr.status); } }; xhr.send(formData); // ← 发送 FormData }