从0到1聊聊网络请求(Ajax、Axios、Fetch)

76 阅读8分钟

前言

鸽了好久没写文章了,最近准备开始学习Vue3的源码、设计模式、算法、小程序、app等。这篇文章致力于让小白迅速上手,不涉及源码等难理解的内容。主要聊一聊原生Ajax请求,到Axios工具库,最后聊一聊ES6提出的Fetch。如果看完还有疑惑的,欢迎在评论区留言,一起讨论,共同进步!

背景

我记得我在刚进公司实习的时候,对于网络请求没有什么概念,都用着大佬封装好的Axios(我那时候连Axios是什么都不知道,更别说Ajax了),由于GET和POST的请求参数不同,像GET请求不能发送请求体(body),这就导致我经常获取不到后端返回的正确数据。
说了这么多,其实就是想表达网络请求挺重要的,几乎每个页面都需要用到!

什么是网络请求?

通俗地来说,就是我们前端同学调用API,请求后端同学的URL路径,顺便把后端同学需要的参数一起携带上,然后就等着后端同学给我们响应。这个过程,我认为就是一次网络请求(至于更深层次的,涉及到协议,计算机网络的就不讨论了,其实是我不懂,后续等我学习下,再把这段内容替换掉哈哈哈)。一旦得到了成功的响应,我们前端同学再拿到对应的数据进行展示。

什么是Ajax?

Ajax(异步的JS和XML)不是一门新的语言,它其实是还是JS。它能在网页不刷新的时候发送HTTP请求并获取响应。

Ajax的优缺点?

  1. 优点:
    • 【我认为最大的优点】不刷新页面。这个可以减少等待时间,大大提高用户的体验感;
    • 可以根据用户的事件来更新部分页面(如:列表搜索,只刷新列表的内容,并没有刷新整个网页)。
  2. 缺点:
    • 不能回退(浏览器不能前进、后退);
    • 存在跨域问题;
    • 对于SEO(爬虫)不友好(爬虫只能爬取静态写死的页面,如关键字写死。对于JS动态操作onClick等用户操作或者通过Ajax网络请求到的资源,爬虫很难爬到)。

手写一个原生Ajax请求

// 1.创建一个请求对象
const xhr = new XMLHttpRequest(); // xhr是实例对象:获取该原型对象XMLHttpRequest上的属性和方法
xhr.responseType = 'json'; // 设置响应体的类型为json格式
xhr.timeout = 2000; // 设置超时时间2000ms(2s)
xhr.ontimeout = () => {}; // 网络请求超时的友好提示
xhr.onerror = () => {}; // 网络异常的友好提示

// 2.设置请求方法和请求路径url
// xhr.open('GET', 'http://127.0.0.1:8080/test'); 请求方法也可以为POST,PATCH,DELETE,PUT
// xhr.open('GET', 'http://127.0.0.1:8080/test?a=100&b=200'); 其中?a=100&b=200为查询字符串
xhr.open('POST', 'http://127.0.0.1:8080/test');
// [补充] xhr.open('GET', `http://127.0.0.1:8080/test?t=${Date.now()}`); 解决浏览器缓存问题

// 3.发送请求,直接send()也是可以的,即不传递请求体参数
// xhr.send(); send方法里面可以传递请求体参数,一般用json格式的比较多,需要传递一个字符串类型的参数
const data = JSON.stringfy({
  name: 'cxxi',
  content: '欢迎大家一起来学习Ajax'
});
xhr.send(data);

// 4.绑定事件,处理响应结果
xhr.onreadystatechange = () => {
  // readystate有五种状态:0 1 2 3 4
  // change 改变(会触发四次,第一次为0的时候不触发)
  if (xhr.readystate === 4) {
    // readystate为4的时候,说明得到了服务端的响应
    if (xhr.status >= 200 && xhr.status < 400) {
      // 设置当状态码在[200, 400)之间的时候,允许处理响应体
      xhr.status; // 响应状态码  200
      xhr.statusText; // 响应信息 OK
      xhr.getAllResponseHeaders(); // 所有响应头信息
      xhr.response; // 响应体 body
      xhr.abort(); // 可以取消请求,避免请求重复发送
    }
  }
};

什么是Axios?

通过手写一个Ajax请求之后,相信大家已经加深了印象,不就是调API嘛!其实Axios是基于Ajax,在它的基础上进一步封装,并且与Promise良性结合在一起的一个工具库。这个库封装了很多东西,开箱即用,当然也可以对这个库自己再封装。

为什么要使用Axios?

至于为什么要使用它,更多原因是别人封装好的,总比自己不断去写原生强吧~它更能实现快速开发。当然在用这个库之前,需要安装下,即yarn add axios 或者 npm i axios。(你可以不用写 -D 或者 --dev,因为打包上线后它也会转化成原生写法)

Axios的优势?

  • 请求响应拦截器
  • 请求响应数据转换(Promise)
  • 保护组织跨站攻击等

手写一个Axios请求

// 1.导入axios(会去node_modules中寻找axios文件夹下的package.json然后进入index.js)
import axios from 'axios';

// 2.发送一个最简单的axios请求(可选参数详见官网)
axios({
  method: 'GET', // 还可以是POST,PUT,PATCH,DELETE
  url: 'http://127.0.0.1:8080/test',
  params: {} // 查询字符串,可选参数
});

// 这个用法和上面是基本一致的
axios.request({
  method: 'POST',
  url: 'http://127.0.0.1:8080/test',
  data: {} // 请求体,可选参数等
});

// 这三种写法选择一种就行
axios.get('http://127.0.0.1:8080/test', { params }, config); // params为查询字符串参数,config为其他参数

-----------------------到上面为止,一个最简单的请求以及三种书写方式就介绍完了----------------------

// 3.其他一些小优化(axios的默认配置,详见官网)
axios.defaults.baseURL = 'http://127.0.0.1:8080'; // 最终URL = baseURL + 请求路径url
axios.defaults.timeout = 2000; // 设置请求超时时间,单位毫秒(ms)
axios.get('/test'); // 这个最终url = http://127.0.0.1:8080/test

// 当有像不同服务器发送请求的需求时,设置默认值就达不到效果了,因为默认值只能向一个服务器发送请求
// 4.为了解决上述问题,可以使用创建实例的方法(创建两个实例然后导出)
const test1 = axios.create({
  baseURL: 'http://127.0.0.1:8080'
});

const test2 = axios.create({
  baseURL: 'http://127.0.0.1:5173'
});

export { test1, test2 }; // 当我们要向8080端口发送请求的时候导入{ test1 }即可,5173端口导入{ test2 }

// 5.axios对比ajax的新特性:请求、响应拦截器
axios.interceptors.request.use(config => { // 如果设置多个,则后进先执行
  // 请求成功,可以请改config内的参数
  return config;
}, error => {
  // 失败
  return Promise.reject(error);
});

axios.interceptors.response.use(response => { // 如果设置多个,先进先执行
  // 响应成功
  return response;
}, error => {
  // 失败
  return Promise.reject(error);
});

-----------------------下面书写一个axios的取消请求,避免重复请求-----------------------
import { test1 as axios } from '@上面创建的test1实例';
const handleCancel = async () => {
  let cancel = null; // 2.用来判断请求是否取消
  if (!cancel) cancel(); // 4.取消请求。当本次请求成功或者失败的时候,下次请求不会进入if语句
  try {
    const dataSource = await axios.get('/test', {
      // 为了更清楚看到效果,避免请求成功或者失败重置cancel导致进不到if语句,可以设置延迟时间
      timeout:5000// 1.配置cancelToken对象
      cancelToken: new axios.CancelToken(c => {
        // 3.赋值
        cancel = c;
      });
    });
    if (dataSource.data) cancel = null; // 如果成功响应,并且data不为null(有数据),则重置
  } catch (e) {
    cancel = null; // 如果请求失败了,则重置
    console.log(e);
  }
};

Axios源码目录结构

  1. axios文件夹下
    • dist文件夹为打包后的文件
    • lib核心文件夹 - 所有源码都放在这个文件夹中
      • adapters 适配器
      • http.js 服务端请求
      • xhr.js 前端ajax请求
    • cancel取消相关的文件夹
      • Cancel.js 取消时的错误对象
      • CancelToken.js 取消请求的构造函数
      • isCancel.js 检查参数是否为取消对消
    • core核心功能的文件夹
      • Axios.js 存放Axios构造函数
      • buildFullPath.js 构造完整url
      • createError.js 创建error对象
      • dispatchRequest.js 创建发送请求(调用两个适配器之一)
      • enhanceError.js 更新错误对象的函数文件
      • InterceptorManager.js 拦截器对象
      • mergeConfig.js 合并对象
      • settle.js 根据状态码改变状态
      • transformData.js 对结果的转换(格式化)
    • helpers 帮助(功能函数)
      • bind.js 改变函数运行时的指向
      • buildURL.js 设置URL
      • combineURLs.js URL合并
    • cookies.js 对cookie的操作
      • deprecatedMethod.js 没有用过的
    • isAbosulteURL.js 判断绝对路径
    • isURLSameOrigin.js 判断是否同源(同一个服务)
    • normalizeHeaderName.js 对头信息统一,大写
    • parseHeaders.js 头信息解析
    • spread.js 对ajax请求进行批量结果的处理
    • axios.js 真正的入口文件
    • index.js 入口文件
    • defaults.js 默认配置对象 (axios.default.timeout = )
    • utils.js 很多的工具函数
  2. axios.js(真正的入口文件分析)
    1. 将方法复制到目标函数中的显示原型链上
    2. 函数自调用
    3. 或者实例化对象然后调用实例对象隐式原型链上的方法
    4. 然后导出改方法

手写一个Fetch请求

// Fetch是一种新的获取资源的接口方式,并不是对XMLHttpRequest的封装。
// 而Axios是对XMLHttpRequest的封装。
Fetch('http://127.0.0.1:8000/test', {
  // 第一个参数是url,第二个参数是可配置的参数(具体配置项需要查阅)
  method: 'POST', // 请求方法
  headers: {}, // 请求头信息配置
  body: JSON.stringfy({}), // 请求体为Nodejs中的Buffer类型或者字符串类型或者表单类型
  mode: 'no-cors' // 是否允许跨域,同源请求。same-origin(同源请求)、no-cors(默认)和cors(允许跨域请求)
});

总结

现在手写原生Ajax请求基本上我是不怎么写了,除非遇到一些扫码页面,需要缩短响应时间,尽量不在html页面中引入Axios的cdn形式。Axios是我现在用得比较多的,在项目中使用起来非常方便。对于Fetch而言,因为Fetch是浏览器原生支持,不需要额外使用第三方库,减小体积。如果是在浏览器控制台测试,或者想快速请求的话,可以使用Fetch,因为它不需要导入,是浏览器支持的。

预言

哈哈哈哈哈,这次就不预言啦,等把上次预言的内容(债)清一清,大概涉及NodejsVite