Axios

34 阅读6分钟

Promise对象

JS优雅解决异步操作的库:频繁的回调只会让代码可读性和调试难度变得更差。

Promise状态:pending(初始),fulfiled(成功),rejected(失败)

用法:

  1. 创建一个异步处理数据的函数(传递resolve,reject) 处理成功调用resolve(异步操作数据),失败调用reject(异步操作失败数据)

  2. 创建使用异步处理函数构建Promise对象

  3. 使用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统一捕获错误

  1. async :声明一个异步函数,直接返回promise对象
  2. 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
    }