从http到Axios和fetch

/ 前端

乜嘢是HTTP

HTTP,全称为HyperText Transfer Protocol(超文本传输协议),是互联网上应用最为广泛的一种网络协议。它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器,并且支持多媒体内容的传输,如图片、视频等。

基本概念

工作流程

HTTP的工作流程包括四个主要步骤:

在这个过程中,客户端和服务端之间交换的消息被称为请求和响应。请求是由接受方——通常是Web浏览器——发起的,而响应则是由服务器发出的回复。

HTTP的特点

HTTP方法

HTTP协议定义了几种不同的方法(也称作“动作”),用于以不同方式操作指定的资源。最常用的包括:

HTTP请求报文

一个HTTP请求报文通常由四部分组成:请求行、请求头部、空行以及请求数据(可选)

  1. 请求行(Request Line):这是请求报文的第一行,包含了三个元素:

    • 请求方法(如GET、POST等),指示要执行的操作类型。
    • 请求的URL或路径,指向目标资源的位置。
    • HTTP协议版本号,表示使用的HTTP协议版本。

    例如,GET /index.html HTTP/1.1 表示使用GET方法请求根目录下的index.html文件,并且使用的是HTTP/1.1版本。

  2. 请求头部(Headers):紧接着请求行之后是一系列的头部字段,它们提供了关于请求的更多信息,比如客户端支持的内容类型、语言偏好、编码方式等。每个头部字段都由名称和值组成,以冒号分隔。

  3. 空行(Blank Line):在所有头部字段之后是一个空行,它标志着头部部分的结束和可能存在的请求体部分的开始。

  4. 请求数据(Optional Request Body):对于某些请求方法(如POST),这里会包含发送给服务器的数据,比如表单数据或者文件上传内容。

HTTP响应报文

响应报文也有类似的结构,但它的起始行被称为状态行,而不是请求行。

  1. 状态行(Status Line):包括三部分:

    • HTTP协议版本号。
    • 状态码,三位数字代码,用于描述请求的结果(例如200代表成功,404代表未找到资源)。
    • 原因短语,是对状态码的文字解释。
  2. 响应头部(Response Headers):类似于请求头部,这些字段提供额外的信息,比如响应的日期、内容长度、内容类型等。

  3. 空行:同样,在所有的响应头部之后有一个空行。

  4. 响应体(Response Body):如果有的话,这部分包含了实际返回给客户端的数据,比如HTML文档、图片或者其他类型的文件。

HTTP常见响应状态码

HTTP响应状态码是服务器在接收到客户端请求后返回的一个三位数字代码,它用来表示请求的处理情况。这些状态码分为五类,每类都有其特定的意义和用途。以下是各类常见的HTTP状态码及其含义:

1xx(信息性状态码)

2xx(成功状态码)

3xx(重定向状态码)

4xx(客户端错误状态码)

5xx(服务端错误状态码)

URL?

URL,全称为Uniform Resource Locator,即统一资源定位符,是用于标识互联网上资源位置的字符串。它允许用户通过网络访问网页、图片、视频等资源。URL是URI(统一资源标识符)的一个种类,专门用来表示资源的位置。

一个标准的URL通常由以下几个部分组成:

  1. 协议(Scheme):指定使用的通信协议,比如HTTP(超文本传输协议)、HTTPS(安全的超文本传输协议)、FTP(文件传输协议)等。协议后面跟有一个冒号和两个斜杠(://)。

    示例:https://

  2. 主机名(Host):指明了资源所在的服务器地址,可以是域名(如www.example.com)或IP地址。

    示例:www.example.com

  3. 端口号(Port):指定了与服务器连接时所使用的端口。大多数时候,如果使用的是默认端口(例如HTTP默认80,HTTPS默认443),则不需要明确写出端口号。

    示例::8080

  4. 路径(Path):指出请求的具体资源在服务器上的位置。它模拟了一个文件系统路径的概念。

    示例:/path/to/resource

  5. 查询字符串(Query String):以问号(?)开始,后面跟着一系列键值对,用来向服务器传递参数。

    示例:?key=value&foo=bar

  6. 片段标识符(Fragment Identifier):以井字号(#)开头,指向文档中的某一部分,主要用于HTML页面内部导航。

    示例:#section1

1
https://www.example.com:8080/path/to/resource?key=value#section1

在这个例子中,https 是协议,www.example.com 是主机名,:8080 是端口号,/path/to/resource 是路径,?key=value 是查询字符串,而 #section1 是片段标识符。

关于回调函数

什么是回调函数?

回调函数是一种编程模式,它允许一个函数作为参数传递给另一个函数,并在特定条件下被调用。

这种机制使得代码可以更加灵活和动态,因为它允许外部定义的行为在内部逻辑的关键点上被执行。

回调函数的主要用途包括:

  1. 处理异步操作:在网络请求、文件读取等场景中,这些操作通常是异步进行的,即它们不会立即完成。为了获取这些操作的结果,我们可以将一个回调函数作为参数传递给负责执行该操作的函数。当异步操作完成后,这个回调函数就会被调用,并传入操作的结果作为参数。
  2. 确保代码顺序执行:即使在同步代码中,回调函数也可以用来保证某些函数只有在另一些函数执行完毕后才会被执行。
  3. 事件监听器:在前端开发中,回调函数常用于设置事件监听器。比如,当你想在用户点击按钮时执行特定代码,你可以将这段代码封装在一个函数中,并将其作为回调函数添加到按钮的点击事件上。这样,每当用户触发点击事件时,相应的回调函数就会自动执行。
  4. 数据处理:回调函数还可以用于数组的数据处理方法中,如map()filter()reduce()等。这些方法接受一个回调函数作为参数,该回调函数定义了如何对数组中的每个元素进行转换或其他操作。

举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function fetchData(callback) {
// 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();

// 初始化请求
xhr.open('GET', 'https://api.example.com/data', true);

// 设置响应类型为 JSON
xhr.responseType = 'json';

// 设置回调函数来处理服务器响应
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) { // 请求成功完成
callback(null, xhr.response); // 调用回调函数并传递结果数据
} else if (xhr.readyState === 4) { // 请求完成但有错误发生
callback(new Error("Request failed with status " + xhr.status), null);
}
};

// 发送请求
xhr.send();
}

// 定义回调函数
function handleData(error, data) {
if (error) {
console.error("获取数据时出错:", error.message);
return;
}
console.log("获取的数据是: ", data);
}

// 调用fetchData,并传递回调函数handleData作为参数
fetchData(handleData);

我的理解:先在fetchData里面判断服务器响应状态,再将情况作为参数(callback)回调给handleData,此时handleData再执行

具体步骤如下:

  1. 调用fetchData(handleData):首先,您调用了fetchData函数,并将handleData作为参数传入。此时,handleData并未执行,只是被当作一个函数指针或引用传给了fetchData

  2. 初始化和发送请求:在fetchData内部,创建了一个XMLHttpRequest对象并配置了相应的请求细节(如方法、URL等),然后发送了请求。在这个阶段,程序不会等待服务器响应,而是继续执行后续代码(如果有),因为这是一个异步操作。

  3. 监听状态变化:通过设置xhr.onreadystatechange,为可能发生的事件(即服务器响应到达时)注册了一个监听器。这个监听器会在每次xhr.readyState发生变化时被触发。

  4. 处理服务器响应

    • xhr.readyState变为4且xhr.status为200时,表示请求成功完成。此时,使用callback(null, xhr.response)调用之前传入的回调函数(即handleData),并将获取的数据作为参数传递。
    • 如果请求完成但出现了错误(例如,xhr.status不是200),则调用callback(new Error("..."), null),以错误信息作为第一个参数传递给回调函数。
  5. 执行回调函数handleData:只有当服务器响应到达,并且根据响应的状态决定如何调用回调之后,才会执行handleData。它会检查是否有错误发生,并相应地处理数据或错误信息。

值得注意的是

此外,值得注意的是,虽然回调函数提供了灵活性,但过度使用会导致“回调地狱”问题——即多层嵌套的回调函数导致代码难以维护和理解。

什么是AJAX

AJAX(Asynchronous JavaScript and XML)是一种用于创建快速动态网页的技术。

主要特点

工作流程

  1. 创建XMLHttpRequest对象:这是进行异步通信的核心对象,负责发送请求并接收响应。

  2. 发送请求到服务器:可以通过GET或POST方法向服务器发送请求,传递必要的参数。

  3. 服务器处理请求并返回响应:服务器端脚本处理请求,并将结果以HTML、XML、JSON等形式返回给客户端。

  4. 更新网页内容:根据从服务器接收到的数据,使用JavaScript和DOM更新网页的部分内容。

应用场景

对,是promise

Promise的概念

Promise是异步编程的一种解决方案,通俗讲,Promise是一个许诺、承诺,是对未来事情的承诺,承诺不一定能完成,但是无论是否能完成都会有一个结果。

在JavaScript中,它代表了一个异步操作的最终完成(或失败)及其结果值。一个Promise对象有三种状态:

  1. Pending(进行中):初始状态,既不是成功,也不是失败。
  2. Fulfilled(已成功):操作成功完成。
  3. Rejected(已失败):操作失败。

一旦一个Promise被resolved(无论是fulfilled还是rejected),它的状态就会被锁定,并且结果值或错误信息将不可改变。

Promise的优点

  1. 更好的错误处理:使用try/catch机制和链式.catch()方法使得错误处理更加直观和易于管理。
  2. 避免回调地狱:相比于嵌套的回调函数,Promise允许你以更线性、更清晰的方式编写异步代码。
  3. 链式调用:可以方便地链接多个异步操作,每个操作都等待前一个操作完成后才开始,并能够传递数据。
  4. 内置支持:现代浏览器和Node.js环境对Promise提供了原生支持,无需额外库。

Promise的缺点

  1. 复杂度:虽然Promise简化了某些类型的异步编程问题,但对于初学者来说,理解Promise的状态转换和如何正确使用它们可能具有一定的挑战性。
  2. 无法取消:一旦创建了一个Promise,就无法取消它。这意味着如果需要停止某个异步操作,必须依赖外部逻辑来实现。
  3. 调试困难:尽管Promise改善了错误处理,但当出现错误时,定位到具体的出错点可能不如同步代码那样直观。
  4. 资源管理:在某些情况下,特别是在涉及大量并发异步操作时,确保正确的资源管理和释放可能会变得复杂。

Promise的基本使用

创建一个Promise

要创建一个新的Promise实例,你需要使用new Promise()构造函数,它接受一个执行器函数(executor function)作为参数。这个执行器函数本身又接受两个参数:resolvereject

1
2
3
4
5
6
7
8
9
10
11
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = true; // 假设这是一个异步操作的结果
if (success) {
resolve("操作成功"); // 成功时调用resolve
} else {
reject("操作失败"); // 失败时调用reject
}
}, 1000); // 模拟延迟1秒
});

使用Promise

一旦创建了Promise,你可以通过.then()方法来处理操作成功的情况,通过.catch()方法来处理操作失败的情况。

1
2
3
4
5
6
7
myPromise
.then(result => {
console.log(result); // 输出: "操作成功"
})
.catch(error => {
console.error(error); // 如果有错误发生,则输出错误信息
});

链式调用

Promise支持链式调用,这使得你可以轻松地将多个异步操作链接在一起。每个.then()方法都会返回一个新的Promise,这意味着你可以在前一个操作完成后开始下一个操作,并且可以从前一个操作的结果中获取数据传递给下一个操作。

1
2
3
4
5
6
7
8
9
10
11
myPromise
.then(result => {
console.log(result); // 输出: "操作成功"
return "新的数据"; // 返回新的数据
})
.then(newResult => {
console.log(newResult); // 输出: "新的数据"
})
.catch(error => {
console.error(error); // 如果有错误发生,则在这里捕获
});

在这个例子中,第二个.then()会接收到前一个.then()返回的数据。

错误处理

为了更好地处理错误,通常建议在Promise链的末尾添加一个.catch(),这样任何未被捕获的错误都可以在这里进行处理。

1
2
3
4
5
6
7
8
9
10
11
myPromise
.then(result => {
console.log(result);
throw new Error("故意抛出的错误"); // 故意抛出一个错误
})
.then(newResult => {
console.log(newResult);
})
.catch(error => {
console.error("捕获到错误:", error.message); // 输出: "捕获到错误: 故意抛出的错误"
});

这里,即使错误是在中间的.then()中发生的,也会被最后的.catch()捕捉到。

asyncawait

在现代JavaScript中,asyncawait 提供了一种更加简洁和直观的方式来处理异步操作。它们使得代码看起来更像同步代码,从而提高了可读性和维护性。

1) Promise ==> 异步操作的基础

Promise 是 JavaScript 中处理异步操作的一个重要概念,它代表了一个异步操作的最终完成(或失败)及其结果值。通过 Promise,我们可以更清晰地管理异步代码流,而不是使用传统的回调函数方式。

2) await ==> 将异步转为同步风格

3) async ==> 标识异步函数

语法概览

实例演示

假设我们需要从API获取一些数据,并根据这些数据做进一步处理:

1
2
3
4
5
6
7
8
9
10
11
12
async function fetchAndProcessData() {
try {
let response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('Network response was not ok');
let data = await response.json(); // 解析JSON数据
console.log(data);
} catch (error) {
console.error('Failed to fetch data:', error);
}
}

fetchAndProcessData();

在这个例子中,我们首先调用 fetch 函数发起网络请求。由于 fetch 返回的是一个 Promise,我们可以直接使用 await 来等待这个 Promise 完成。如果请求成功,我们会进一步解析响应体中的 JSON 数据;如果请求失败或遇到异常,会通过 catch 块捕获并处理错误。

注意点

  1. await 只能在 async 函数中使用:尝试在非异步函数中使用 await 将会导致语法错误。

  2. 错误处理:当使用 await 时,推荐使用 try...catch 结构来捕获可能的异常。因为异步函数内的任何未捕获的错误都会导致返回的 Promise 被拒绝。

  3. 并发执行:如果你有多个独立的异步任务需要同时执行,可以考虑使用 Promise.all() 来等待所有任务完成。这样可以提高性能,因为它允许任务并行运行。

    1
    2
    3
    4
    async function getData() {
    let [data1, data2] = await Promise.all([fetchData1(), fetchData2()]);
    console.log(data1, data2);
    }
  4. 理解返回值:记住,async 函数总是返回一个 Promise。即使你在函数中直接返回了一个普通值,它也会被自动包装进一个已解决的 Promise 中。

  5. 调试:由于 await 暂停了函数的执行,这可能会给调试带来一定的复杂性。确保你理解每个 await 的位置以及它如何影响代码的执行流程。

明白了,我会将您最初提供的内容与详细的解释、示例和额外的信息整合在一起,以便为您提供一个全面的关于XMLHttpRequest的指南。

XMLHttpRequest

XMLHttpRequest(简称XHR)是一个用于在浏览器和服务器之间传输数据的JavaScript对象。它允许您发送请求并处理响应,而无需重新加载整个页面,从而实现动态网页内容更新。

概述

XMLHttpRequest最早由微软引入,作为ActiveX对象的一部分,后来被其他浏览器厂商采纳并标准化,成为了现代Web开发中处理异步数据交换的核心技术之一。

主要属性与方法

基本模板步骤

  1. 创建XMLHttpRequest对象:这是发起请求的第一步。
  2. 调用open方法:初始化一个新的请求或重置一个现有的请求。
  3. 绑定事件监听器:通常是在readystatechange事件上设置回调函数来处理服务器响应。
  4. 调用send方法:发送请求到服务器。对于GET请求,参数通常是null;对于POST请求,则需要传递数据。

完整示例

以下是一个按照上述步骤编写的完整示例,展示了如何使用XMLHttpRequest发送一个GET请求并处理响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();

// 2. 使用 open 方法初始化请求
xhr.open('GET', 'https://api.example.com/data', true);

// 设置响应类型为 JSON(可选)
xhr.responseType = 'json';

// 3. 绑定事件监听器
xhr.onreadystatechange = function() {
// 检查请求是否完成并且成功
if (xhr.readyState === 4) { // readyState 4 表示请求已完成
if (xhr.status >= 200 && xhr.status < 300) { // 成功状态码范围
console.log('Response:', xhr.response);
} else {
console.error('Error: ', xhr.status, xhr.statusText);
}
}
};

// 可以在这里设置请求头(适用于 POST 请求等)
// xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');

// 4. 发送请求
xhr.send(null); // 对于 GET 请求,send 参数通常是 null

特别的:xhr.statusxhr.statusText,它们是在处理XMLHttpRequest对象的响应时自动提供的属性,而不是需要您主动接受的参数

关键点总结

Axios

概述

Axios 是一个基于 Promise 的 HTTP 客户端,适用于浏览器和 Node.js 环境。它允许开发者通过简洁的 API 发起 HTTP 请求,并且支持拦截请求和响应、取消请求、自动转换 JSON 数据等特性 。

历史背景

Axios 诞生于需要更现代化的方式来处理网络请求的需求之中,特别是在 JavaScript 中使用 Promise 来简化异步编程的问题。在 Axios 出现之前,开发者通常依赖于 XMLHttpRequest 对象或者 jQuery 提供的 AJAX 方法来进行网络通信。然而,这些方法往往伴随着回调地狱的问题,而 Axios 通过提供基于 Promise 的接口解决了这一问题 。

创建一个实例

1
2
3
4
5
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});

请求配置

这些是创建请求时可以用的配置选项。只有 url 是必需的。如果没有指定 method,请求将默认使用 GET 方法。

下面是一个简化的例子,仅保留了最常用的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
// `url` 是用于请求的服务器 URL
url: '/user',

// `method` 是创建请求时使用的方法,默认为 'get'
method: 'get',

// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
baseURL: 'https://some-domain.com/api/',

// 自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},

// `params` 是与请求一起发送的 URL 参数
params: {
ID: 12345
},

// `data` 是作为请求体被发送的数据,适用于 'PUT', 'POST', 'DELETE', 'PATCH' 请求方法
data: {
firstName: 'Fred'
},

// `timeout` 指定请求超时的毫秒数。
timeout: 1000,

// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false,

// `responseType` 表示浏览器将要响应的数据类型,默认为 'json'
responseType: 'json',

// `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
validateStatus: function (status) {
return status >= 200 && status < 300;
}
}

**此外,如果您发现某些配置(如 headers, baseURL, timeout)在多数请求中都是相同的,可以考虑创建一个 Axios 实例并设置默认配置,这样就不需要在每次请求时都指定它们。**例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Requested-With': 'XMLHttpRequest'}
});

// 使用实例发起请求
instance.get('/user', {
params: {
ID: 12345
}
})
.then(response => console.log(response))
.catch(error => console.error(error));

响应结构

一个请求的响应包含以下信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
// `data` 由服务器提供的响应
data: {},

// `status` 来自服务器响应的 HTTP 状态码
status: 200,

// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',

// `headers` 是服务器响应头
// 所有的 header 名称都是小写,而且可以使用方括号语法访问
// 例如: `response.headers['content-type']`
headers: {},

// `config` 是 `axios` 请求的配置信息
config: {},

// `request` 是生成此响应的请求
// 在node.js中它是最后一个ClientRequest实例 (in redirects),
// 在浏览器中则是 XMLHttpRequest 实例
request: {}
}

当使用 then 时,您将接收如下响应:

1
2
3
4
5
6
7
8
axios.get('/user/12345')
.then(function (response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});

当使用 catch,或者传递一个rejection callback作为 then 的第二个参数时,响应可以通过 error 对象被使用,正如在错误处理部分解释的那样。

默认配置

您可以指定默认配置,它将作用于每个请求。

全局 axios 默认值

1
2
3
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

自定义实例默认值

1
2
3
4
5
6
7
// 创建实例时配置默认值
const instance = axios.create({
baseURL: 'https://api.example.com'
});

// 创建实例后修改默认值
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;

配置的优先级

配置将会按优先级进行合并。它的顺序是:在lib/defaults.js中找到的库默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。后面的优先级要高于前面的。

1
2
3
4
5
6
7
8
9
10
11
12
// 使用库提供的默认配置创建实例
// 此时超时配置的默认值是 `0`
const instance = axios.create();

// 重写库的超时默认值
// 现在,所有使用此实例的请求都将等待2.5秒,然后才会超时
instance.defaults.timeout = 2500;

// 重写此请求的超时时间,因为该请求需要很长时间
instance.get('/longRequest', {
timeout: 5000
});

拦截器

在请求或响应被 then 或 catch 处理前拦截它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});

如果你稍后需要移除拦截器,可以这样:

1
2
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

可以给自定义的 axios 实例添加拦截器。

1
2
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
// `error.request` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例
console.log(error.request);
} else {
// 发送请求时出了点问题
console.log('Error', error.message);
}
console.log(error.config);
});

使用 validateStatus 配置选项,可以自定义抛出错误的 HTTP code。

1
2
3
4
5
axios.get('/user/12345', {
validateStatus: function (status) {
return status < 500; // 处理状态码小于500的情况
}
})

使用 toJSON 可以获取更多关于HTTP错误的信息。

1
2
3
4
axios.get('/user/12345')
.catch(function (error) {
console.log(error.toJSON());
});

组件封装

为了提高代码的可维护性和复用性,常常需要对 Axios 进行封装。封装可以包含设置默认配置(如 baseURL)、添加请求/响应拦截器等功能 。

创建 Axios 实例

首先,创建一个 Axios 实例并设置一些默认值:

1
2
3
4
5
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});

请求拦截器

可以在请求发送前对请求进行处理,比如添加 token:

1
2
3
4
5
6
7
instance.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => Promise.reject(error));

响应拦截器

可以对响应数据进行统一处理,例如简化错误处理逻辑:

1
2
3
4
5
6
instance.interceptors.response.use(response => response.data, error => {
if (error.response && error.response.status === 401) {
// 处理未授权错误
}
return Promise.reject(error);
});

Axios 在 Vue.js 中的应用

在 Vue.js 项目中,可以通过创建一个专门的文件来封装 Axios 实例,并将其挂载到 Vue 实例上以便全局使用 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// src/utils/request.js
import axios from 'axios';

const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
});

request.interceptors.request.use(config => {
// 在发送请求之前做些什么
return config;
}, error => {
// 对请求错误做些什么
return Promise.reject(error);
});

request.interceptors.response.use(response => {
// 对响应数据做点什么
return response.data;
}, error => {
// 对响应错误做点什么
return Promise.reject(error);
});

export default request;

// main.js
import Vue from 'vue';
import App from './App.vue';
import request from './utils/request';

Vue.prototype.$http = request;

new Vue({
render: h => h(App),
}).$mount('#app');

// 在组件内使用
this.$http.get('/user').then(response => {
console.log(response);
});

Fetch API

Fetch API 是一个现代的网络请求工具,它允许你通过 JavaScript 异步地请求资源,而不需要使用传统的 XMLHttpRequest 对象。Fetch API 提供了更强大和灵活的功能集,并且基于 Promise 实现。

基本用法

fetch()的功能与 XMLHttpRequest 基本相同,但有三个主要的差异。

(1)fetch()使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁。

(2)fetch()采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。

(3)fetch()通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。

Response 对象:处理 HTTP 回应

Response 对象的同步属性

fetch()请求成功以后,得到的是一个 Response 对象。它对应服务器的 HTTP 回应。

1
const response = await fetch(url);

前面说过,Response 包含的数据通过 Stream 接口异步读取,但是它还包含一些同步属性,对应 HTTP 回应的标头信息(Headers),可以立即读取。

1
2
3
4
5
async function fetchText() {
let response = await fetch('/readme.txt');
console.log(response.status);
console.log(response.statusText);
}

标头信息属性有下面这些:

Response.ok

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

Response.status

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

Response.statusText

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

Response.url

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

Response.type

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

Response.redirected

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

判断请求是否成功

fetch()发出请求以后,有一个很重要的注意点:只有网络错误,或者无法连接时,fetch()才会报错,其他情况都不会报错,而是认为请求成功。

这就是说,即使服务器返回的状态码是 4xx 或 5xx,fetch()也不会报错

1
2
3
4
5
6
7
8
async function fetchText() {
let response = await fetch('/readme.txt');
if (response.status >= 200 && response.status < 300) {
return await response.text();
} else {
throw new Error(response.statusText);
}
}
1
2
3
4
5
if (response.ok) {
// 请求成功
} else {
// 请求失败
}

读取内容的方法

Response对象根据服务器返回的不同类型的数据,提供了不同的读取方法。

上面读取方法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据。

response.text()

response.json()

response.formData()

response.blob()

response.arrayBuffer()

fetch()的第二个参数:定制 HTTP 请求

fetch()的第一个参数是 URL,还可以接受第二个参数,作为配置对象,定制发出的 HTTP 请求。

1
fetch(url, optionObj)

HTTP 请求的方法、标头、数据体都在这个对象里面设置。

(1)POST 请求

1
2
3
4
5
6
7
8
9
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body: 'foo=bar&lorem=ipsum',
});

const json = await response.json();

上面示例中,配置对象用到了三个属性。

(2)提交 JSON 数据

1
2
3
4
5
6
7
8
const user =  { name:  'John', surname:  'Smith'  };
const response = await fetch('/article/fetch/post/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(user)
});

(3)提交表单

1
2
3
4
5
6
const form = document.querySelector('form');

const response = await fetch('/users', {
method: 'POST',
body: new FormData(form)
})

fetch()配置对象的完整 API

fetch()第二个参数的完整 API 如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const response = fetch(url, {
method: "GET",
headers: {
"Content-Type": "text/plain;charset=UTF-8"
},
body: undefined,
referrer: "about:client",
referrerPolicy: "no-referrer-when-downgrade",
mode: "cors",
credentials: "same-origin",
cache: "default",
redirect: "follow",
integrity: "",
keepalive: false,
signal: undefined
});

fetch()请求的底层用的是 Request() 对象的接口,参数完全一样,因此上面的 API 也是Request()的 API。

这些属性里面,headersbodymethod前面已经给过示例了,下面是其他属性的介绍。

cache

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

mode

mode属性指定请求的模式。可能的取值如下:

credentials

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

跨域请求发送 Cookie,需要将credentials属性设为include

1
2
3
fetch('http://another.com', {
credentials: "include"
});

signal

signal属性指定一个 AbortSignal 实例,用于取消fetch()请求,详见下一节。

keepalive

keepalive属性用于页面卸载时,告诉浏览器在后台保持连接,继续发送数据。

一个典型的场景就是,用户离开网页时,脚本向服务器提交一些用户行为的统计信息。这时,如果不用keepalive属性,数据可能无法发送,因为浏览器已经把页面卸载了。

1
2
3
4
5
6
7
window.onunload = function() {
fetch('/analytics', {
method: 'POST',
body: "statistics",
keepalive: true
});
};

redirect

redirect属性指定 HTTP 跳转的处理方法。可能的取值如下:

integrity

integrity属性指定一个哈希值,用于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值。

比如,下载文件时,检查文件的 SHA-256 哈希值是否相符,确保没有被篡改。

1
2
3
fetch('http://site.com/file', {
integrity: 'sha256-abcdef'
});

referrer

referrer属性用于设定fetch()请求的referer标头。

这个属性可以为任意字符串,也可以设为空字符串(即不发送referer标头)。

1
2
3
fetch('/page', {
referrer: ''
});

referrerPolicy

referrerPolicy属性用于设定Referer标头的规则。可能的取值如下:

FetchAxios选哪个?

选择使用 fetch 还是 axios 主要取决于你的具体需求、项目环境以及个人偏好。

优点:

  1. 内置支持:Fetch 是浏览器内置的,不需要额外引入库。
  2. 基于 Promise:提供了基于 Promise 的 API,使得异步操作更加直观易懂。
  3. 轻量级:没有额外的依赖,适合对包大小敏感的项目。

缺点:

  1. 不支持旧版浏览器:虽然现代浏览器都支持 fetch,但如果你需要兼容一些老旧版本的浏览器(如IE),可能需要引入polyfill。
  2. 默认不处理错误:对于 HTTP 错误状态码(如 404 或 500),fetch 不会拒绝 Promise,这可能会导致错误处理变得复杂。
  3. 功能相对简单:与 axios 相比,fetch 在处理某些高级特性时可能不如 axios 方便,例如自动转换 JSON 数据、取消请求等。

Axios

优点:

  1. 广泛的浏览器兼容性:Axios 支持所有主流浏览器,包括 IE。
  2. 更丰富的功能:提供了诸如拦截请求和响应、转换请求和响应数据、取消请求等功能。
  3. 更好的错误处理:Axios 对于 HTTP 错误状态码会自动拒绝 Promise,简化了错误处理逻辑。
  4. 易于使用的API:具有更加人性化和简洁的 API 设计,尤其是在处理复杂的请求配置时更为方便。

缺点:

  1. 增加项目体积:相对于原生的 fetch 来说,引入 axios 会增加项目的总体积。
  2. 需要额外安装:需要通过 npm 或者 yarn 等包管理工具进行安装,或者直接在 HTML 中通过 <script> 标签引入。