前端请求发展史Ajax、jQuery Ajax、fetch、axios(使用+对比+避坑指南)

简介

大神请直接划走,如果你对请求模棱两可、或者知道有哪些请求库,但是呢只熟练其中的一两种,并不全会。来,这篇文章就非常适合你。

本文讲解了前端发展史上请求库的一个变迁,从最开始的Ajax、到后来的jQuery Ajax、再到fetch、再到axios。笔者会详细介绍各个请求库的使用,以及坑点。最后会总结它们各自的优缺点。我相信看完本文,你一定会有所收获。也欢迎各位小伙伴点赞收藏,我相信后面你一定会用得到。

本文所有实例的请求接口在文章末尾,使用 node + express 搭建

在说请求库之前,我们先来回忆一下表单。

表单

大部分情况下,网页中展示的数据或者图片或者多媒体效果都是静态的数据,但是有时用户需要通过网页跟服务器进行动态的数据交互,例如登录、注册等操作,这时需要用到表单。

表单其实是由一些输入框、单选、复选框等元素组成,用户点击提交按钮,浏览器会将这些数据发送给表单设置的 URL ,URL 对应的服务器接收到数据并处理之后,再将数据返回给浏览器。

表单提交

表单提交方式一般有两种:

第一种是使用typesubmit的按钮进行提交。

第二种是获取表单,然后手动调用submit()方法进行提交。

submit按钮提交

<h3>form1 直接提交</h3>
<form action="http://localhost:3000/test" onsubmit="submit1()">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="submit" />
</form>

页面渲染如下,点击type="submit"的提交按钮就可以提交我们的表单。提交后会自动触发我们的onsubmit属性对应的方法,这里就是会触发submit1方法。

image.png

手动调用submit方法提交

<h3>form2 手动提交</h3>
<form action="http://localhost:3000/test" id="form2" onsubmit="submit1()">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <!-- 这个按钮可以放在表单内也可以放在表单外 -->
  <input type="button" id="submitBtn2" value="提交" onclick="handleSubmit()"/>
</form>

js里面,我们还可以在按钮的事件里面获取到表单,然后调用表单的submit()方法进行表单的提交。

handleSubmit = function () {
    
    
  const form2 = document.getElementById("form2");
  form2.submit();
};

页面渲染如下,点击提交按钮,然后手动通过方法submit()就可以提交我们的表单啦。注意这种方式是不会触发onsubmit属性对应的方法的。

image.png

这里我们是给button直接绑定的点击事件,当然你也可以通过获取到button然后给button绑定事件的方式来进行处理。

// 方式1
const submitBtn2 = document.getElementById("submitBtn2");
submitBtn2.onclick = function () {
    
    
  const form2 = document.getElementById("form2");
  form2.submit();
};

// 方式2
const submitBtn2 = document.getElementById("submitBtn2");
submitBtn2.addEventListener("click", function () {
    
    
  const form2 = document.getElementById("form2");
  form2.submit();
});

因为表单的提交每次都会刷新页面,体验并不是很好,所以后面就出来了AjaxAjax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。为了后面能顺利进行Ajax请求,我们肯定是要把表单提交完后默认进行页面跳转这个行为阻止的。所以我们再来看看阻止表单提交都有哪些方式呢?

阻止表单提交

阻止表单提交方式一般有三种:

第一种是给onsubmit绑定的方法返回false来阻止表单提交。

第二种是给提交按钮返回false来阻止表单提交。

第三种是阻止提交按钮默认行为来阻止表单提交。

onsubmit返回false

我们先来看看第一种

<form action="http://localhost:3000/test" onsubmit="return submit1()">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="submit" id="submitBtn1" />
</form>

onsubmit对应的方法里返回false就可以阻止表单提交了

function submit1() {
    
    
  return false;
}

提交按钮返回false

<form action="http://localhost:3000/test">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="submit" id="submitBtn2" onclick="return handleSubmit()" />
</form>

在提交按钮对应的方法返回false就可以了。跟上面的有点类似。

function handleSubmit() {
    
    
  return false;
}

当然你也可以使用绑定事件的方式

<form action="http://localhost:3000/test">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="submit" id="submitBtn2" />
</form>

通过给提交按钮绑定事件,然后在事件里面返回false也能阻止表单提交。注意在addEventListener绑定事件这种方式下返回false无效的。

const submitBtn2 = document.getElementById("submitBtn2");
submitBtn2.onclick = function () {
    
    
  return false;
};

// 无效 无 效无效 无效
const submitBtn2 = document.getElementById("submitBtn2");
submitBtn2.addEventListener("click", function () {
    
    
  return false;
});

阻止提交按钮默认行为

我们还可以阻止提交按钮的默认事件来阻止表单提交。

<form action="http://localhost:3000/test">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="submit" id="submitBtn2" onclick="handleSubmit()" />
</form>

在提交按钮对应的方法里面使用preventDefault阻止默认事件。

// 这种方式需要window.event来获取事件对象
function handleSubmit(e) {
    
    
  const event = e || window.event;
  event.preventDefault();
}

当然你也可以使用绑定事件的方式

<form action="http://localhost:3000/test">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="submit" id="submitBtn2" />
</form>

通过给提交按钮绑定事件,然后在事件里面使用preventDefault阻止默认事件。

// 方式1
const submitBtn2 = document.getElementById("submitBtn2");
submitBtn2.onclick = function (e) {
    
    
  e.preventDefault();
};

// 方式2
const submitBtn2 = document.getElementById("submitBtn2");
submitBtn2.addEventListener("click", function (e) {
    
    
  e.preventDefault();
});

说完表单的提交,我们再来说说表单另外一块非常重要的知识点enctype

enctype 属性

enctype 用于定义表单数据提交到服务器的过程中如何对数据进行编码,可选值有:

  1. application/x-www-form-urlencoded;
  2. multipart/form-data;
  3. text/plain

application/x-www-form-urlencoded

默认方式是第一种 application/x-www-form-urlencoded,当使用 form 表单提交数据时,会默认转成key=value&key=value这种格式的数据。

对于get请求,会将表单参数生成?key=value&key=value这样的链接,拼在当前action链接的后面进行请求。对于post请求,会将表单参数生成key=value&key=value这样的链接,放在请求体中进行请求。

并且对于参数中的一些字符(中文、特殊字符等)会进行编码。

为什么要对提交的数据进行编呢?

当使用 form 表单提交数据时,需要对数据进行编码,因为 URL 的数据是通过 ?key=value&key=value& 的方式传输给服务器,这其中有一些作为功能性质的特殊字符例如 & ? =,如果数据中带有这些字符而且未进行编码的话,可能会导致传输数据发生错误,例如原本是 ?key=value ,如果 value 是 123&456 的话,那么 结果服务器中接收到的 value 值变成123,且多出一个 key=456,这种编码方式叫做 URL 百分号编码,其标准收录在 RFC3986 中。

multipart/form-data

当设置成 multipart/form-data 时,浏览器不对字符进行编码,这种编码方式通常适用于上传文件

对于multipart/form-data格式的请求数据,会将数据通过特殊标记进行切分,然后放在请求体中进行传递。

text/plain

使用第三种方式 text/plain 时,浏览器将请求参数放入 body 作为字符串处理,这种方式用于传输原生的 HTTP 报文,不适用任何格式化处理。

Ajax

接下来我们来看看改变历史的Ajax。因为表单的提交每次都会刷新页面,体验并不是很好,Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。所以Ajax的出现,无疑改变了历史。使用户的体验大大提升。

我们这里说的原生Ajax主要介绍的是 XmlHttpRequest,通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据。

基础流程

我们先来看看发送一个最简单的Ajax需要哪些步骤。

创建请求对象

首先需要创建请求对象,创建的时候需要考虑浏览器的兼容性。

let xhr;
if (window.XMLHttpRequest) {
    
    
  //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
  xhr = new XMLHttpRequest();
} else {
    
    
  // IE6, IE5 浏览器执行代码
  xhr = new ActiveXObject("Microsoft.XMLHTTP");
}

监听请求

在发送请求之前,我们最好先对请求进行监听,readystatechange 事件它会在请求的各个阶段被执行。

image.png

一般我们只需要关注4这个状态。

// 设置状态监听函数
xhr.onreadystatechange = function () {
    
    
  // 4成功
  if (this.readyState !== 4) return;

  // xhr对象,在这里获取响应结果
  console.log(this);
};

当一个 XMLHttpRequest 请求被 abort() 方法取消时,其对应的 readystatechange 事件不会被触发。

初始化请求

XMLHttpRequest.open(method,url,async) 方法初始化一个新创建的请求,或重新初始化一个请求。

  • method:请求的类型;GET 或 POST
  • url:请求地址
  • async:true(异步)或 false(同步),默认是true
xhr.open(
  "get",
  `http://localhost:3000/testGet?name=randy&age=27` // 接口在文章末尾,使用node + express搭建
);

设置返回值类型

创建完请求后,我们还需要设置响应数据的类型,如果不设置将会使用默认值text

常用返回值格式如下

  1. “” 与 text一样
  2. text
  3. json
  4. blob
  5. document
  6. arraybuffer

这里我们响应是json,所以我们需要设置为josn

xhr.responseType = "json";

发送请求

XMLHttpRequest.send() 方法用于发送 HTTP 请求。如果是异步请求(默认为异步请求),则此方法会在请求发送后立即返回;如果是同步请求,则此方法直到响应到达后才会返回(也就是会阻止后续代码执行)。

XMLHttpRequest.send() 方法接受一个可选的参数,其作为请求主体;如果请求方法是 GET 或者 HEAD,则应将请求主体设置为 null。

这里因为我们是get请求,所以我们发送一个null就可以了。

xhr.send(null)

send()方法支持的数据格式如下,在post请求时会用到。

XMLHttpRequest.send();
XMLHttpRequest.send(String); // 传递js对象需要转成json字符串
XMLHttpRequest.send(URLSearchParams data);
XMLHttpRequest.send(FormData data);
XMLHttpRequest.send(ArrayBuffer data);
XMLHttpRequest.send(ArrayBufferView data);
XMLHttpRequest.send(Blob data);
XMLHttpRequest.send(Document data);
XMLHttpRequest.send(DOMString? data);

这些请求数据格式需要熟知,不然传递不支持的数据格式会导致请求出错!

获取响应结果

响应其实是在我们的onreadystatechange监听事件里面,我们来看看成功的xhr对象是怎么样的。

image.png

这里我们重点关注response、status、statusText、readyState这几个值即可。

response 属性返回响应的正文。返回的类型为 ArrayBufferBlobDocumentJavaScript Object 或 字符串中的一个。这取决于请求的 responseType 属性。如果没有指定,结果默认是text类型。

status是http状态码。

statusText是http状态信息。

readyState是当前请求对象的状态。

深入

前面我们举了一个最简单的get请求例子,下面我们再深入了解下。比如post请求、异步、取消请求、错误处理等。

发送post请求

对于post请求的参数是需要放在send()方法里面,并且支持多种数据格式。下面笔者使用频率最高的三种数据格式来进行讲解。

我们先来创建一个表单,这里我们不再使用表单来提交数据,而是使用Ajax。所以这个表单只是方便我们获取用户输入的数据。(当然你也可以不使用表单)。

<h3>form1</h3>
<form id="form1">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="button" id="submitBtn1" onclick="request1()" value="提交" />
</form>

<h3>结果</h3>
<div id="result"></div>

我们再在请求方法request1中创建一个xhr对象,并初始化请求和监听请求。

function request1() {
    
    
  // 创建xhr
  let xhr;
  if (window.XMLHttpRequest) {
    
    
    //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
    xhr = new XMLHttpRequest();
  } else {
    
    
    // IE6, IE5 浏览器执行代码
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
  }

  // 设置状态监听函数
  xhr.onreadystatechange = function () {
    
    
    // 4成功
    if (this.readyState !== 4) return;

    // xhr对象
    console.log(this);
    // 将响应结果放到div显示
    const resultDiv = document.getElementById("result");
    resultDiv.innerText = this.response;
  };

  // 初始化请求
  xhr.open("post", `http://localhost:3000/testPost`);
}

请求体默认的数据格式是text/plain,对于这种数据我们开发中并不常用,一般会使用下面这三种数据格式,那怎么修改我们请求的数据格式呢?那就得使用setRequestHeader方法了,也就是设置我们的请求头。

请求数据格式为application/x-www-form-urlencoded

// 设置我们请求数据的格式
// 对于 发送数据方式2,这个设置请求数据可以免去,因为它会自动设置
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");

// 获取表单数据
const formData = new FormData(document.getElementById("form1"));
const name = formData.get("name");
const age = formData.get("age");

// 发送数据1
xhr.send(`name=${
      
      name}&age=${
      
      age}`);

// 发送数据方式2 直接借助 URLSearchParams 免去手动拼接参数
// const urlSearchParams = new URLSearchParams(formData);
// xhr.send(urlSearchParams);

我们来看看post请求效果,结果成功返回。

89edd7ad-cfca-4a5c-a4b3-a4759d85d550.gif

请求数据格式为application/josn

// 设置请求数据类型
xhr.setRequestHeader("Content-type","application/json");

// 获取表单数据
const formData = new FormData(document.getElementById("form1"));
const name = formData.get("name");
const age = formData.get("age");

// 发送json数据 需要转成字符串
xhr.send(JSON.stringify({
    
     name, age }));

我们来看看post请求效果,结果成功返回。

02b616d8-b509-4542-8d4f-5336669dd8ff.gif

请求数据格式为multipart/form-data

我们一般使用这种数据格式来上传文件,并不会直接用来提交简单表单数据。

// 可以不设置 因为new FormData会自动将请求头设置为这种格式
xhr.setRequestHeader("Content-type","multipart/form-data");

// 获取表单数据
const formData = new FormData(document.getElementById("form1"));

// 初始化请求 使用第二个接口 使用multer特殊处理
xhr.open("post", `http://localhost:3000/testPost2`);

// 发送json数据 需要转成字符串
xhr.send(formData);

我们来看看post请求效果,结果也成功返回。

02b616d8-b509-4542-8d4f-5336669dd8ff.gif

异步

Ajax默认是异步的,当我们open方法的第三个参数设置为false的时候,会将我们的请求设置为同步。很多小伙伴以为同步就是响应结果是send方法的返回值,其实并不是的。这里的同步和异步只是代表阻塞不阻塞后续代码的运行。响应结果都是需要在readystatechange监听事件中获取的。

我们来看两个例子

先来看看异步

xhr.open(
  "get",
  `http://localhost:3000/testGet?name=${
      
      name}&age=${
      
      age}`,
  true
);

const result = xhr.send(null);
console.log(result);
console.log("后续代码");

来看看输出,异步请求是请求后不会阻塞后续代码运行,并且send方法是不会返回结果的。

image.png

再来看看同步

xhr.open(
  "get",
  `http://localhost:3000/testGet?name=${
      
      name}&age=${
      
      age}`,
  false
);

// 阻塞后续代码运行
const result = xhr.send(null);
console.log(result);
console.log("后续代码");

来看看输出,同步请求是会阻塞后续代码的运行的,直到请求有了结果后续代码才会运行,并且send方法是不会返回结果的。

image.png

取消请求

如果该请求已被发出,XMLHttpRequest.abort() 方法将终止该请求。当一个请求被终止,它的 readyState将被置为 0,并且请求的 status 置为 0。

xhr.abort();

image.png

错误处理

当请求遇到错误时,将触发error 事件。不过需要注意的是后端返回的错误并不会触发onerror事件,比如后端返回的500 400等。只有在网络中断或者跨域时才会触发onerror事件。

// 设置请求失败时的监听函数
// 当网络中断或者跨域时会触发onerror事件
xhr.onerror = function () {
    
    
  console.error(this);
};

Ajax总结

  1. 整个请求过程从创建、监听、初始化请求、发送请求还是相当繁琐的,开发使用起来效率肯定是不高的。
  2. 请求方式比较有限,只支持get、post请求
  3. 不支持 promise,请求结果需要通过监听函数获取,对于有依赖关系的请求容易产生嵌套
  4. 请求传递的数据格式比较有限,不支持目前主流的原生js对象(需要转成json字符串)。
  5. 需要手动设置响应返回值类型,不然默认返回text类型数据
  6. 支持取消请求

jQuery Ajax

jQuery Ajax并不是一个新东西,而是对原生Ajax的一个封装,使其使用起来更方便、开发起来更快速。

$.ajax()

之前使用ajax发送一个请求,需要经过创建、监听、初始化请求、发送请求等一系列步骤,非常麻烦。在jQuery中,一个方法就能搞定。

我们来看个例子。

首先在页面引入jQuery

<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>

然后创建一个表单。

<h3>form1</h3>
<form id="form1">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="button" id="submitBtn1" onclick="request2()" value="提交" />
</form>
<h3>结果</h3>
<div id="result"></div>

当点击提交按钮的时候,我们发送一个get请求到后端服务器。

function request2() {
    
    
  $.ajax({
    
    
    url: "http://localhost:3000/testGet", // 规定发送请求的 URL。默认是当前页面。
    data: $("#form1").serialize(), // 规定要发送到服务器的数据。
    success(result) {
    
    
      console.log(result);
      $("#result").text(JSON.stringify(result));
    },
  });
}

我们来看效果。数据正常获取并返回

image.png

下面我们以post请求为例,来详细讲解下$.ajax()方法。

function request2() {
    
    
  const formData = new FormData(document.getElementById("form1"));
  const name = formData.get("name");
  const age = formData.get("age");

  $.ajax({
    
    
    type: "POST", // 规定请求的类型(GET 或 POST) 默认GET
    url: "http://localhost:3000/testPost2", // 规定发送请求的 URL。默认是当前页面。
    // data: formData, // 上传文件需要用到,传送formData会自动设置contentType: "multipart/form-data",我们需要设置contentType: false、processData: false
    // data: urlSearchParams, // 直接传递 URLSearchParams 对象 我们需要设置processData: false
    data: `name=${
      
      name}&age=${
      
      age}`, // 规定要发送到服务器的数据。支持这种search字符串类型。可以使用$("#form1").serialize()直接序列化出来
    // data: $("#form1").serialize(), // 把表单序列化成search字符串
    // data: $.param({ name, age }), // 把对象序列化成search字符串
    // data: { name, age }, // 支持对象类型
    // data: JSON.stringify({ name, age }), // 支持对象字符串类型。使用这种方式需要设置 contentType: "application/json"
    // contentType: "application/x-www-form-urlencoded", // 发送数据到服务器时所使用的内容类型。默认是:"application/x-www-form-urlencoded"。
    // contentType: "application/json", // 发送数据到服务器时所使用的内容类型。默认是:"application/x-www-form-urlencoded"。
    // contentType: false, // 不设置,当data是FormData是需要设置。
    dataType: "", // 预期的服务器响应的数据类型。如果不指定,jQuery 将自动根据 HTTP 包 MIME 信息来智能判断。可以设置json、text、xml、html等
    // processData: false, // 布尔值,规定通过请求发送的数据是否转换为查询字符串。默认是 true。
    beforeSend(xhr) {
    
    
      // 发送请求前运行的函数
      console.log(xhr);
    },
    success(result) {
    
    
      // 请求成功运行的函数
      console.log(result);
      $("#result").text(JSON.stringify(result));
    },
    error(xhr, status, error) {
    
    
      // 如果请求失败要运行的函数
      // 后端返回错误码也会进入该方法 比如 400/401/500
      console.log(error);
    },
    complete(xhr, status) {
    
    
      // 请求完成时运行的函数,不管成功或者失败
      console.log(status);
    },
  });
}

这里我们重点关注datacontentTypeprocessData

data只支持URLSearchParamsFormDataname=${name}&age=${age}这种search字符串、对象、对象字符串五种形式。并且需要和contentType相对应。

contentType: "application/json"时传递的data只能是对象字符串形式。

contentType: "application/x-www-form-urlencoded"时传递的data可以是search字符串还可以是对象,因为对象会被自动转化成search字符。

search字符串我们可以手动拼出来,当然还有更便捷的方法,那就是使用$.serialize()和$.param()方法。

data: $("#form1").serialize() // 表单序列化
data: $.param({
    
     name, age }) // 对象序列化

当我们传递的data参数是URLSearchParams格式时,需要将processData设置为false,关闭自动转换。否则会报错。

当我们传递的data参数是FormData格式时(一般是在上传文件的时候),需要将processData设置为false,关闭自动转换。并且还需要将contentType设置为false。否则会报错。

当然如果你觉得上面的$.ajax()方法太麻烦,我们还可以使用对应请求的便捷方法$.get()$.post()$.getJSON()

$.get()

$.get(URL,data,function(data,status,xhr),dataType) 方法使用 HTTP GET 请求从服务器加载数据。它有四个参数。

  1. URL 必需。规定您需要请求的 URL。

  2. data 可选。规定连同请求发送到服务器的数据。只支持查询字符串和对象两种类型

  3. function 可选。规定当请求成功时运行的函数。

  4. dataType 可选。规定预期的服务器响应的数据类型。默认地,jQuery 会智能判断

这里我们简单试用下

$.get(
  "http://localhost:3000/testGet",
  $("#form1").serialize(),
  function success(data, status, xhr) {
    
    
    console.log(data);
    $("#result").text(JSON.stringify(data));
  },
  "json"
);

能正确获取到结果

image.png

$.post()

$.post(URL,data,function(data,status,xhr),dataType) 方法使用 HTTP POST 请求从服务器加载数据。它有四个参数。

  1. URL 必需。规定您需要请求的 URL。

  2. data 可选。规定连同请求发送到服务器的数据。只支持查询字符串和对象两种类型

  3. function 可选。规定当请求成功时运行的函数。

  4. dataType 可选。规定预期的服务器响应的数据类型。默认地,jQuery 会智能判断

这里我们简单试用下

$.post(
  "http://localhost:3000/testPost",
  $("#form1").serialize(),
  function success(data, status, xhr) {
    
    
    console.log(data);
    $("#result").text(JSON.stringify(data));
  },
  "json"
);

能正确获取到结果

image.png

$.getJSON()

$.getJSON(url,data,function(data,status,xhr)) 方法使用 AJAX 的 HTTP GET 请求获取 JSON 数据。它有三个参数。

  1. URL 必需。规定您需要请求的 URL。

  2. data 可选。规定连同请求发送到服务器的数据。只支持查询字符串和对象两种类型

  3. function 可选。规定当请求成功时运行的函数。

这里我们简单试用下

$.getJSON(
  "http://localhost:3000/testGet",
  $("#form1").serialize(),
  function success(data, status, xhr) {
    
    
    console.log(data);
    $("#result").text(JSON.stringify(data));
  }
);

能正确获取到结果

image.png

可以发现,虽然便捷方法使用起来更方便,但是限制还是比较多,特别是数据传递格式那块,只支持查询字符串和对象两种类型。并且对于请求错误也没有回调函数,只有成功的回调函数。

jQuery Ajax总结

  1. 相较原生Ajax,封装了一系列方法,使用起来便捷了不少。
  2. 相较原生Ajax,请求数据格式有了改善,支持原生的js对象。会自动将原生对象转成search字符串传递。
  3. 不支持promise,请求结果需要通过回调函数获取,对于有依赖关系的请求容易产生嵌套。
  4. 不强制需要手动设置响应返回值类型,jQuery Ajax会自动根据返回结果进行设置。
  5. 好像不支持取消请求

fetch

fetch(url, config) 方法用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。

fetch()并不是对原生Ajax的封装,而是浏览器原生支持的一个请求库。

我们先来看一个简单的例子

fetch(`http://localhost:3000/testGet?name=randy&age=27`, {
    
    })
  .then((response) => {
    
    
    console.log(response);
    return response.json();
  })
  .then((res) => {
    
    
    console.log(res);
  });

输出responseres

image.png

可以发现,fetch()请求完后并不是直接返回结果,而是一个Response 对象。它对应服务器的 HTTP 回应。这个对象我们后面会重点介绍。response是一个 Stream 对象,我们需要调用它的一些方法才能得到我们想要的数据。比如上面我们调用了response.json(),这个方法的功能就是将数据转换成json格式的数据。

response对象

ok

Response.ok属性返回一个布尔值,表示请求是否成功,true对应 HTTP 请求的状态码 200 到 299,false对应其他的状态码。

status

Response.status属性返回一个数字,表示 HTTP 回应的状态码(例如200,表示成功请求)。

statusText

Response.statusText属性返回一个字符串,表示 HTTP 回应的状态信息(例如请求成功以后,服务器返回"OK")。

url

Response.url属性返回请求的 URL。如果 URL 存在跳转,该属性返回的是最终 URL。

type

Response.type属性返回请求的类型。可能的值如下:

  • basic:普通请求,即同源请求。
  • cors:跨域请求。
  • error:网络错误,主要用于 Service Worker。
  • opaque:如果fetch()请求的type属性设为no-cors,就会返回这个值。
  • opaqueredirect:如果fetch()请求的redirect属性设为manual,就会返回这个值。

redirected

Response.redirected属性返回一个布尔值,表示请求是否发生过跳转。

headers

Response 对象还有一个Response.headers属性,指向一个 Headers 对象,对应 HTTP 回应的所有标头。

Headers 对象提供了以下方法,用来操作标头。

  • Headers.get():根据指定的键名,返回键值。
  • Headers.has(): 返回一个布尔值,表示是否包含某个标头。
  • Headers.set():将指定的键名设置为新的键值,如果该键名不存在则会添加。
  • Headers.append():添加标头。
  • Headers.delete():删除标头。
  • Headers.keys():返回一个遍历器,可以依次遍历所有键名。
  • Headers.values():返回一个遍历器,可以依次遍历所有键值。
  • Headers.entries():返回一个遍历器,可以依次遍历所有键值对([key, value])。
  • Headers.forEach():依次遍历标头,每个标头都会执行一次参数函数。

比如获取响应Content-Type

response.headers.get('Content-Type'); // application/json; charset=utf-8

读取内容的方法

前面我们说了,响应内容是在response.body上面,但是body是一个ReadableStream对象,我们需要进一步转换才能得到我们想要的值。

那转换都有哪些方法呢?我们可以根据服务器返回的不同类型的数据,调用不同的读取方法。

  • response.text():得到文本字符串。
  • response.json():得到 JSON 对象。
  • response.blob():得到二进制 Blob 对象。
  • response.formData():得到 FormData 表单对象。
  • response.arrayBuffer():得到二进制 ArrayBuffer 对象。

config参数

fetch(url, config)有两个参数,url是请求地址,这个就不再多说了,我们重点来看看config

这里笔者将介绍几个比较重要的config参数。

method

method:HTTP 请求的方法,GETPOSTDELETEPUT都在这个属性设置。

headers

headers:一个对象,用来定制 HTTP 的标头。比如常用的content-type

body

body:POST 请求的数据体。Get和Head请求不能设置该参数

我们来看看fetch支持哪些请求数据类型呢?我们来看笔者测试的例子。

fetch(`http://localhost:3000/testPost`, {
    
    
  method: "POST",
  // body: "纯文本",
  // body: formData, // 会自动设置 content-type 为 multipart/form-data
  // body: urlSearchParams, // 会自动设置 application/x-www-form-urlencoded
  // body: `name=${name}&age=${age}`, // 需要手动设置 "content-type": "application/x-www-form-urlencoded"
  body: JSON.stringify({
    
     name, age }), // 需要手动设置 "content-type": "application/json"
  // 还支持  Blob 或 arrayBuffer,使用的比较少 这个笔者就不举例了
  headers: {
    
    
    // "content-type": "application/x-www-form-urlencoded",
    "content-type": "application/json",
  },
})
  .then((response) => {
    
    
    console.log(response);
    // 转成字符串文本
    return response.text();
  })
  .then((res) => {
    
    
    const resultDiv = document.getElementById("result");
    resultDiv.innerText = res;
  });

fetch支持 text、FormData、URLSearchParams、search字符串、json字符串、Blob、arrayBuffer格式的请求数据类型,并且body默认情况下会以'text/plain;charset=UTF-8格式发送请求数据。

当数据格式为FormData时会自动设置 content-typemultipart/form-data 的请求头。

当数据格式为URLSearchParams时会自动设置 content-typemultipart/x-www-form-urlencoded 的请求头。

传递json格式数据需要转成json字符串,并且还需要设置 "content-type": "application/json" 的请求头。

mode

mode:指定请求的模式。可取值有三个:cors、same-origin、no-cors

  • cors:默认值,允许跨域请求。
  • same-origin:只允许同源请求。
  • no-cors:请求方法只限于 GET、POST 和 HEAD,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交表单所能发出的简单请求。

cache

cache属性指定如何处理缓存。可取值如下:

  • default:默认值,先在缓存里面寻找匹配的请求。
  • no-store:直接请求远程服务器,并且不更新缓存。
  • reload:直接请求远程服务器,并且更新缓存。
  • no-cache:将服务器资源跟本地缓存进行比较,有新的版本才使用服务器资源,否则使用缓存。
  • force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器。
  • only-if-cached:只检查缓存,如果缓存里面不存在,将返回504错误。

credentials

credentials属性指定是否发送 Cookie。可能的取值如下:

  • same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送。
  • include:不管同源请求,还是跨域请求,一律发送 Cookie。
  • omit:一律不发送。

怎么判断请求是否成功

fetch()发出请求以后,有一个很重要的注意点:只有网络错误,或者无法连接时,fetch()才会报错,其他情况都不会报错,而是认为请求成功。这就是说,即使服务器返回的状态码是 4xx 或 5xx,fetch()也不会报错(即 Promise 不会变为 rejected状态)。

那怎么判断请求是否成功

第一种方法是通过Response.status属性,得到 HTTP 回应的真实状态码,判断请求是否成功。也就是判断status是否在200-300之间。

async function fetchTest() {
    
    
  let response = await fetch(`http://localhost:3000/testGet?name=randy&age=27`);
  if (response.status >= 200 && response.status < 300) {
    
    
    // 请求成功
    return await response.json();
  } else {
    
    
    // 请求失败
    throw new Error(response.statusText);
  }
}

另外一种方法就是判断response.ok是否为true。因为status200-300之间的时候ok的值是true

if (response.ok) {
    
    
 // 请求成功
} else {
    
    
 // 请求失败
}

取消请求

fetch()请求发送以后,如果中途想要取消,也是支持的,需要使用AbortController对象。

下面我们来演示一下

let controller = new AbortController();
let signal = controller.signal;

fetch(`http://localhost:3000/testGet?name=${
      
      name}`, {
    
    
  signal: controller.signal, // 需要传递signal参数
});

// 监听取消
// 也可以通过`controller.signal.aborted`属性判断取消信号是否已经发出。
signal.addEventListener("abort", () => console.log("signal abort!"));

// 取消
controller.abort();

cfcf3a2d-a253-4e55-94f1-da7ee6a43ed9.gif

这里需要注意的是controller、signalfetch请求参数里面的signal是一一对应的。你调用哪个controller.abort()就会取消对应的请求。当然你也可以使用同一个controller对应多个请求,这样你就可以一次取消多个请求。

fetch总结

  1. 支持promise,使用更方便、快捷、高效。
  2. 得到的响应需要进一步转化才能得到我们想要的结果。
  3. http状态码的错误需要我们自行判断,因为后端返回400、500等错误码在fetch这边会resolve,当成成功处理。
  4. 请求传递的数据格式有限,不支持原生js对象。
  5. 请求方式也扩充了,不仅支持get、post,还支持 put、delete等请求方式
  6. get、head请求不支持传递请求体参数,参数只能拼接在请求路径后面。
  7. 支持取消请求

我们再来看看目前使用最多的axios

axios

axios 并不是新东西,而是一个基于 promise 对原生 Ajax 封装的 HTTP 库,可以用在浏览器和 node.js 中。

所以在使用之前我们需要先进行引入

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

我们下来看一个简单的例子

<h3>axios form</h3>
<form id="form1">
  <input type="text" name="name" placeholder="请输入名字" />
  <input type="number" name="age" placeholder="请输入年龄" />
  <input type="button" id="submitBtn1" onclick="request4()" value="提交" />
</form>
<h3>结果</h3>
<div id="result"></div>

我们使用axios(config)发送一个请求。

function request4() {
    
    
  const formData = new FormData(document.getElementById("form1"));
  const name = formData.get("name");
  const age = formData.get("age");
  const urlSearchParams = new URLSearchParams(formData);

  axios({
    
    
    url: "http://localhost:3000/testGet",
    params: urlSearchParams,
  }).then((res) => {
    
    
    // 响应数据在data中
    console.log(res.data);
    const resultDiv = document.getElementById("result");
    resultDiv.innerText = JSON.stringify(res.data);
  });
}

我们来看看效果

image.png

对于axios,重点在config和响应结果response,下面我们来逐一分析。

config

这里笔者介绍几个比较重要的config参数。

url

url 是用于请求的服务器 URL

method

HTTP 请求的方法,GETPOSTDELETEPUT都在这个属性设置。

baseURL

baseURL 将自动加在 url 前面,除非 url 是一个绝对 URL。通过设置baseURL可以简化我们url的配置。

headers

设置请求头,比如常用的content-type。这个我们就不多说了。

params

params 是即将与请求一起发送的 URL 参数。必须是一个无格式对象(plain object)或 URLSearchParams 对象。

params里面设置的参数axios会自动转成search字符串,拼接到请求地址后面。

data

data 是作为请求主体被发送的数据。只适用于这些请求方法 ‘PUT’, ‘POST’, 和 ‘PATCH’。

在没有设置 transformRequest 时,必须是以下类型之一:

  • string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  • 浏览器专属:FormData, File, Blob
  • Node 专属: Stream

当数据格式为FormData时 axios 会自动设置 content-typemultipart/form-data 的请求头。

当数据格式为URLSearchParams时 axios 会自动设置 content-typeapplication/x-www-form-urlencoded 的请求头。

当数据格式为plain object时 axios 会自动设置 content-typeapplication/json 的请求头。

当你想以application/x-www-form-urlencoded 格式传送数据的时候,通常有三种方法。

  1. 你可以传递URLSearchParams参数。axios 会自动设置 content-typeapplication/x-www-form-urlencoded 的请求头。
  2. 传递plain object,但是需要你手动设置 content-typeapplication/x-www-form-urlencoded 的请求头。
  3. 通过qs库生成search字符串进行传递。因为当数据格式为string时 axios 会自动设置 content-typeapplication/x-www-form-urlencoded 的请求头。

当你想发送纯字符串,需要手动设置请求头content-typetext/plain

responseType

responseType 表示服务器响应的数据类型,可以是 ‘arraybuffer’, ‘blob’, ‘document’, ‘json’, ‘text’, ‘stream’。设置完后,axios会自动对我们的响应结果进行转换。

timeout

timeout 指定请求超时的毫秒数(0 表示无超时时间)。如果请求花费了超过 timeout 的时间,请求将被中断

withCredentials

withCredentials 表示跨域请求时是否需要携带cookie。默认是false

maxContentLength

maxContentLength 定义允许的响应内容的最大尺寸

配置弄懂之后,我们再来看看响应response

response

data

data 由服务器提供的响应。我们的响应数据就是在这个属性里面获取。

status

status 来自服务器响应的 HTTP 状态。比如成功会返回200。

statusText

statusText 来自服务器响应的 HTTP 状态信息。比如成功会返回"OK"

headers

headers 服务器响应的头

config

请求的配置信息。

request

请求对象,因为axios是对XMLHttpRequest的包装,所以是一个XMLHttpRequest对象。

创建实例

我们一般不会直接使用axios,而是创建axios实例。axios支持使用自定义配置新建一个 axios 实例。

const instance = axios.create({
    
    
  baseURL: '/api',
  timeout: 1000,
  headers: {
    
    'content-type': 'application/json'}
});

// ...

创建实例有两个好处

第一,相同配置我们可以在创建实例的时候配置好,这样后面就不需要重复在每个请求里面配置了。

第二,我们一个系统可以有多个请求实例,每个实例可以有自己独特的配置。

实例方法

每个axios实例都能有自己的request请求方法,并且还有便捷方法。

这里的config会和上面创建实例使用的config合并,相同配置会覆盖。

instance.request(config)

这个方法类似axios(config)

instance.get(url, config)

直接发送一个get请求

instance.delete(url, config)

直接发送一个delete请求

instance.head(url, config)

直接发送一个head请求

instance.options(url, config)

直接发送一个options请求

instance.post(url, data, config)

直接发送一个post请求,第二个参数是请求数据。

instance.put(url, data, config)

直接发送一个put请求,第二个参数是请求数据。

instance.patch(url, data, config)

直接发送一个patch请求,第二个参数是请求数据。

并发方法

axios还支持并发请求。

比如一次性发送两个请求。

axios
  .all([
    axios.get("http://localhost:3000/testGet", {
    
    
      params: {
    
     name: "jack" },
    }),
    axios.get("http://localhost:3000/testGet", {
    
    
      params: {
    
     name: "tom" },
    }),
  ])
  .then(
    axios.spread((res1, res2) => {
    
    
      console.log(res1);
      console.log(res2);
    })
  )
  .catch((err) => {
    
    
    console.log(err);
  });

通过axios.all([请求1, 请求2...])批量发送请求,通过axios.spread((请求1结果,请求2结果...) => {})获取请求结果。注意当请求中有一个失败整个请求都失败,在catch里面会返回错误的那个promise

错误处理

axios会自动分析响应的 http status 码,当status在200到300之间话,会reslove,也就是成功,其它会被reject

当然如果你想自定义成功status范围的话,可以通过配置请求参数validateStatus方法

validateStatus: function (status) {
    
    
    return status >= 200 && status < 300; // 默认是200-300
  },

对于错误的处理,一般有两种方式

// 通过 promise 的 catch
axios("http://localhost:3000/testGet", {
    
    
  params: urlSearchParams,
})
  .then((res) => {
    
    
    console.log(res);
  })
  .catch((err) => {
    
    
    console.log(err);
  });

// 通过 try catch
try {
    
    
  const res = await axios("http://localhost:3000/testGet", {
    
    
    params: urlSearchParams,
  });
  console.log(res);
} catch (err) {
    
    
  console.log(err);
}

取消请求

axios()请求发送以后,如果中途想要取消,也是支持的,需要使用CancelToken对象。总体来说和fetch的配置很类似。

下面我们来演示一下

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios("http://localhost:3000/testGet", {
    
    
  cancelToken: source.token, // 需要传递cancelToken
});

// 取消请求(message 参数是可选的)
source.cancel("Operation canceled by the user.");

cb7d5603-a5e1-469d-8a9f-e27ee5a28448.gif

这里需要注意的是CancelTokenaxios请求参数里面的cancelToken是一一对应的。你调用哪个source.cancel()就会取消对应的请求。当然你也可以使用同一个cancelToken对应多个请求,这样你就可以一次取消多个请求。

axios还支持第二种取消请求的方式

const CancelToken = axios.CancelToken;
let cancel;

axios("http://localhost:3000/testGet", {
    
    
  cancelToken: new CancelToken(function executor(c) {
    
    
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  }),
});

// 取消请求(message 参数是可选的)
cancel("Operation canceled by the user.");

axios相较fetch好像没有取消的监听函数。

axios总结

  1. 支持promise,使用更方便、快捷、高效。
  2. 能通过配置responseType,自动将我们的响应转换成相应的格式,基本不需要手动处理。
  3. http状态码的错误不需要我们自行判断,默认情况下status只有在200-300之间才会当成成功处理。
  4. 请求体data参数支持的数据格式多,并且会根据我们传递数据的格式自动设置请求content-type的值,非常智能。
  5. 请求方式也扩充了,不仅支持get、post,还支持 put、delete等请求方式。并且提供了相应的便捷方法。
  6. 路径参数不强制手动拼接在路径后,可以通过params配置,axios会自动处理。
  7. 提供了请求拦截和响应拦截,可以对请求和响应进行统一处理,开发效率会更高。
  8. 支持取消请求。

如果对axiosTypeScrit中的应用感兴趣的话可以看看笔者之前写的TypeScript实战之用TS封装Axios

后端接口代码

接口很简单,使用 node + express 搭建

const express = require("express");
const app = express();
var cors = require("cors");

// 允许跨域
app.use(cors());

// 解析表单中 application/x-www-form-urlencoded 格式的数据
app.use(express.urlencoded({
    
     extended: false }));

// 解析表单中application/json格式的数据
app.use(express.json());

// 解析表单中multipart/form-data格式的数据
const multer = require("multer");
const upload = multer();

app.get("/testGet", function (req, res, next) {
    
    
  res.json(req.query);
});

app.post("/testPost", function (req, res, next) {
    
    
  // 将请求体数据直接返回
  res.send(req.body);
});

// 特殊处理multipart/form-data格式的数据
app.post("/testPost2", upload.none(), function (req, res, next) {
    
    
  // 将请求体数据直接返回
  res.send(req.body);
});

app.listen(3000, () => {
    
    
  console.log("serve running on 3000");
});

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

猜你喜欢

转载自blog.csdn.net/weixin_38664300/article/details/131008513