1、简单了解 Xhr
XMLHttpRequest
是一个内建的浏览器对象,它允许使用 JavaScript 发送 HTTP 请求。
现如今,我们有一个更为现代的方法叫做 fetch
,它的出现使得 XMLHttpRequest
在某种程度上被弃用。
目前只有一种情况下将使用 Xhr
做一些
fetch
目前无法做到的事情,如跟踪上传进度
。
1.1、发送请求
要发送请求,需要 3 个步骤:
-
创建
XMLHttpRequest
:jslet xhr = new XMLHttpRequest();
此构造器没有参数。
-
初始化它,通常就在
new XMLHttpRequest
之后:jsxhr.open(method, URL)
此方法指定请求的主要参数:
method
------ HTTP 方法。通常是"GET"
或"POST"
。URL
------ 要请求的 URL 请注意,open
调用与其名称相反,不会建立连接。它仅配置请求,而网络活动仅以send
调用开启。
-
发送请求。
jsxhr.send([body])
这个方法会建立连接,并将请求发送到服务器。可选参数
body
包含了 request body。 一些请求方法,像GET
没有 request body。还有一些请求方法,像POST
使用body
将数据发送到服务器。我们稍后会看到相应示例。
1.2、XMLHttpRequest
的状态
readyState 的值及其对应的状态:
值 | 状态 | 描述 |
---|---|---|
0 | UNSENT | 代理被创建,但尚未调用 open 方法 |
1 | OPENED | open 方法已经被调用 |
2 | HEADERS_RECEIVED | send 方法已经被调用,并且头部和状态已经可获得 |
3 | LOADING | 下载中 |
4 | DONE | 下载操作已完成 |
一般而言,我们只关心 xhr 完成请求体下载的状态(即 readyState 为 4)
1.3、获取请求结果
js
const xhr = new XMLHttpRequest();
// 初始化一个新创建的请求
xhr.open("GET", "https://xxx/car");
// 设置响应处理
xhr.onreadystatechange = function () {
// 请求完成
if (xhr.readyState === 4) {
// HTTP状态码为200,请求成功
if (xhr.status === 200) {
// 获取数据
doSomething(xhr.response);
} else {
console.error("GET 请求失败:", xhr.status, xhr.statusText);
}
}
};
// 发送请求
xhr.send();
1.4、请求头设置
setRequestHeader(name, value)
使用给定的 name
和 value
设置 request header。
js
xhr.setRequestHeader('Content-Type', 'application/json');
1.5、上传进度
实战案例
js
<input type="file" onchange="upload(this.files[0])">
<script>
function upload(file) {
let xhr = new XMLHttpRequest();
// 跟踪上传进度
xhr.upload.onprogress = function(event) {
console.log(`Uploaded ${event.loaded} of ${event.total}`);
};
xhr.open("POST", "/article/xmlhttprequest/post/upload");
xhr.send(file);
}
</script>
1.6、跨域设置
默认情况下不会将 HTTP 授权发送到其他域。要启用它们,可以将
xhr.withCredentials
设置为true
:
注意 :服务器端必须在响应头中设置Access-Control-Allow-Credentials: true
,且Access-Control-Allow-Origin
不能为通配符*
,必须指定明确的源(如https://example.com
)。
js
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'http://anywhere.com/request');
.....
1.7、超时机制
js
var xhr = new XMLHttpRequest();
xhr.open("GET", "/server");
// 超时时间,单位是毫秒
xhr.timeout = 2000;
xhr.ontimeout = function (e) {
// XMLHttpRequest 超时。在此做某事。
};
xhr.send();
2、Promise封装Xhr
封装目标
-
使用 Promise 封装 XMLHttpRequest
-
处理 HTTP 状态码
- 在 [200, 300) 区间,以及 304(缓存命中) 时,视为请求成功,Promise 兑现
- 其余状态码视为请求失败,Promise 拒绝
-
处理请求体数据,使用 data 配置项。
-
支持设置请求头
2.1、简单使用
- 通过
XMLHttpRequest
创建实例 - 调用实例
open 方法
,传入请求方法
与URL
- 监听
实例状态改变
- 发送请求
send
js
const xhr = new XMLHttpRequest();
// 初始化一个新创建的请求
xhr.open("GET", "https://xxx/car");
// 设置响应处理
xhr.onreadystatechange = function () {
// 请求完成
if (xhr.readyState === 4) {
// HTTP状态码为200,请求成功
if (xhr.status === 200) {
// 获取数据
doSomething(xhr.response);
} else {
console.error("GET 请求失败:", xhr.status, xhr.statusText);
}
}
};
// 发送请求
xhr.send();
2.2、Promise 化
js
function ajax() {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://xxx/car");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 异步操作完成时,将promise状态改为fulfilled,兑现值为响应体数据
resolve(xhr.response);
} else {
// 拒绝原因为HTTP状态码,具体可根据业务调整
reject(xhr.status);
}
}
};
xhr.send();
});
}
2.3、设置 Http 方法,地址,数据类型
- 设置请求方法
- 设置请求地址(URL)
- 设置数据格式
js
function ajax({
method = "GET",
url,
// 默认情况下,responseType是JSON格式数据
responseType = "json",
}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = responseType;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 经过正确处理的响应体数据
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
};
xhr.send();
});
}
ajax({
url: "https://xxx/car",
})
.then((res) => {
console.log(res);
})
.catch((err) => {
// do something
});
2.4、处理请求头
上文知晓 setRequestHeader(name, value)
js
function ajax({
method = "GET",
url,
// 默认情况下,responseType是JSON格式数据
responseType = "json",
headers = {}
}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = responseType;
// 设置请求头
Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 经过正确处理的响应体数据
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
};
xhr.send();
});
}
ajax({
url: "https://xxx/car",
headers: {
header1: "header1",
},
})
.then((res) => {
console.log(res);
})
.catch((err) => {
});
2.5、处理请求体
以 POST 请求举例,根据请求体数据的类型不同,一般可以分为以下四种媒体类型(MIME type)进行传输:
- application/x-www-form-urlencoded:数据以键值对形式发送,键和值都进行了 URL 编码(name=Jack&age=18)
- multipart/form-data:用于文件上传或需要同时发送文件和数据时。
- text/xml:用于发送 XML 数据。
- application/json:用于发送 JSON 格式的数据。
请求体和用户设置的 content-type 约定如下:
-
用户设置的 content-type 拥有最高优先级
-
用户不设置 content-type 时:
-
如 data 是特殊类型:
- data 是 FormData:直接添加到请求体中,因content-type 涉及 boundary,不额外处理,由浏览器自动设置。
- data 是 URLSearchParams:调用 toString 方法,转化为 url 编码字符串,设置 content-type:application/x-www-form-urlencoded
-
data 是普通对象:转为 JSON 类型,设置 content-type: application/json
-
data 为 text 或其他: 直接添加到请求体中,content-type:text/xml
-
js
function ajax({
method = "GET",
url,
// 默认情况下,responseType是JSON格式数据
responseType = "json",
headers = {}
}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = responseType;
// 设置请求头
Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
// 处理请求体
let requestData = null;
if (data) {
if (data instanceof FormData) {
// data是FormData类型
requestData = data;
} else if (data instanceof URLSearchParams) {
// data是URLSearchParams类型
requestData = data.toString();
// 如用户设置了content-type请求头,则不作修改
if (!headers["content-type"]) {
xhr.setRequestHeader(
"content-type",
"application/x-www-form-urlencoded"
);
}
} else if (typeof data === "object") {
// data为普通对象
// 只转化为JSON或url编码字符串
if (
["application/x-www-form-urlencoded", "application/json"].includes(
headers["content-type"]
)
) {
if (headers["content-type"] === "application/x-www-form-urlencoded") {
const params = new URLSearchParams(data);
requestData = params.toString();
} else {
requestData = JSON.stringify(data);
}
} else {
// 默认情况下转为JSON格式
requestData = JSON.stringify(data);
if (!headers["content-type"]) {
xhr.setRequestHeader("content-type", "application/json");
}
}
} else {
requestData = data;
if (!headers["content-type"]) {
xhr.setRequestHeader("content-type", "text/xml");
}
}
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 经过正确处理的响应体数据
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
};
xhr.send(requestData);
});
}
ajax({
url: "https://xxx/car",
headers: {
header1: "header1",
},
data: {
name: "小明",
},
})
.then((res) => {
console.log(res);
})
.catch((err) => {
});