一、XMLHttpRequest
1.介绍
- 是一个浏览器内置的构造函数,简称XHR
ajax
和axios
都是基于XMLHttpRequest封装的
2.发起XHR的步骤
-
创建 xhr 对象
-
调用 xhr.open() 函数
-
调用 xhr.send() 函数
-
监听 load 事件
get请求格式
javascript
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://abc.com/test/api');
xhr.setRequestHeader('content-type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
_this.getTableData();
}
if (xhr.status === 200 || xhr.status === 304) {
console.log('xhr', xhr);
}
};
xhr.send({ a: 1 });
xhr.addEventListener('load', () => {});
post请求格式
javascript
let xhr = new XMLHttpRequest()
// 发起请求
// 请求行
xhr.open('post', 'http://www.itcbc.com:3006/api/addbook')
// 请求头:post方式传递普通键值对,需要设置content-type编码格式
xhr.setRequestHeader(
'Content-Type',
'application/x-www-form-urlencoded'
)
// 请求体
xhr.send(
`bookname=${booknameV}&author=${authorV}&publisher=${publisherV}`
)
// 接受响应
xhr.addEventListener('load', function () {
console.log(JSON.parse(xhr.response));
})
readyState的含义
ini
enum XHRReadyState {
UNSENT = 0, //代理被创建,但尚未调用 open() 方法。
OPENED = 1, //open() 方法已经被调用。
HEADERS_RECEIVED = 2, //send() 方法已经被调用,并且头部和状态已经可获得。
LOADING = 3, //下载中;responseText 属性已经包含部分数据。
DONE = 4 //下载操作已完成。
}
二、Fetch
介绍
- fetch是window对象
- 会返回一个promise
- 支持流式输出
格式
scss
fetch(resource, options)
post:
javascript
fetch("https://www.test.com", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: "我是body请求体"
})
三、xhr和fetch的区别
功能点 | XMLHttpRequest | Fetch |
---|---|---|
基本的请求能力 | √ | √ |
基本的获取响应能力 | √ | √ |
监控请求进度 | √ | × |
监控响应进度 | √ | √ |
Service Worker中是否可用 | × | √ |
控制cookie的携带 | × | √ |
控制重定向 | × | √ |
请求取消 | √ | √ |
自定义referrer | × | √ |
流 | × | √ |
API风格 | Event | Promise |
活跃度 | 停止更新 | 不断更新 |
四、如何去实现监控请求响应进度
1.fetch实现请求下载文件时的进度条事件
vue
<template>
<button @click="onFetchDownLoad">fetch实现请求下载文件时的进度条事件</button>
<br />
<progress id="file" max="100" :value="fetchProgrss"></progress
><span>{{ fetchProgrss }}%</span> <br />
</template>
<script setup lang="ts">
import streamsaver from "streamsaver";
import { ref } from "vue";
const fetchProgrss = ref<number>(0);
/**
*
* Blob + <a> 方式:
* 需要 先完整下载到内存,然后转换成 Blob,最后用 <a> 标签触发下载 ,
* 大文件容易导致 浏览器崩溃(特别是 1GB 以上的文件)
*
* streamSaver+ fetch:
* 采用 流式写入 (WritableStream),边下载边保存,不会在内存中存储整个文件,避免了大文件导致的 内存溢出 (OOM)
* 适用于 几百 MB、甚至 GB 级别的大文件
*/
const onFetchDownLoad = async () => {
// Step 1: 启动 fetch 并获取 Reader
let response: any = await fetch("./M30T.glb");
const reader = response.body.getReader();
// Step 2: 获取文件总大小 +是转数字
const contentLength = +response.headers.get("Content-Length");
// Step 3: read the data
let receivedLength = 0; // 目前收到了多少字节
let chunks = []; // 接收到的二进制块数组(包括 body)
// 你可以自由实现3
const fileStream = streamsaver.createWriteStream("m30t.glb"); // 创建一个文件可写流
const writer = fileStream.getWriter(); // 获取文件流写入器
while (true) {
//value 是一个 Uint8Array ,表示 接收到的多少字节
// done 是一个布尔值,表示是否读取完毕
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
receivedLength += value.length;
console.log(`Received ${receivedLength} of ${contentLength}`);
const percent = Math.round((receivedLength / contentLength) * 100);
console.log(percent + "%");
fetchProgrss.value = percent;
// 你可以自由实现3
await writer.write(value); //数据逐块写入文件
}
// Step 4: 将块chunks连接成单个 Uint8Array
let chunksAll = new Uint8Array(receivedLength); // (4.1)
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position); // (4.2)
position += chunk.length;
}
// 你可以自由实现1: 解码为字符串
// let result = new TextDecoder("utf-8").decode(chunksAll);
// We're done!
// console.log(20220309231613, result);
// 你可以自由实现2: 将 Uint8Array 转换为 Blob ,进行下载 (占用内存)
// const blob = new Blob([chunksAll], { type: "text/plain" });
// console.log("blob", blob);
// // 创建a标签下载blob
// const a = document.createElement("a");
// a.href = URL.createObjectURL(blob);
// a.download = "m30t.glb";
// a.click();
// 你可以自由实现3: 使用 streamSaver+ fetch 进行下载(不占用浏览器内存,直接写入文件)
// pnpm install streamsaver
await writer.close(); // 关闭文件写入流 触发浏览器弹出文件保存对话框。
};
</script>
<style scoped></style>
2.xhr/axois实现请求下载文件时的进度条事件
vue
<template>
<button @click="onFetchDownLoad">fetch实现请求下载文件时的进度条事件</button>
<br />
<progress id="file" max="100" :value="fetchProgrss"></progress
><span>{{ fetchProgrss }}%</span> <br />
<br />
<br />
<button @click="onXhrDownLoad">xhr实现请求下载文件时的进度条事件</button>
<br />
<progress id="file" max="100" :value="xhrProgrss"></progress
><span>{{ xhrProgrss }}%</span> <br />
<br />
<br />
<button @click="onAxiosDownLoad">axios实现请求下载文件时的进度条事件</button>
<br />
<progress id="file" max="100" :value="axiosProgrss"></progress
><span>{{ axiosProgrss }}%</span> <br />
</template>
<script setup lang="ts">
import streamsaver from "streamsaver";
import { ref } from "vue";
import axios from "axios";
const fetchProgrss = ref<number>(0);
const xhrProgrss = ref<number>(0);
const axiosProgrss = ref<number>(0);
/**
*
* Blob + <a> 方式:
* 需要 先完整下载到内存,然后转换成 Blob,最后用 <a> 标签触发下载 ,
* 大文件容易导致 浏览器崩溃(特别是 1GB 以上的文件)
*
* streamSaver+ fetch:
* 采用 流式写入 (WritableStream),边下载边保存,不会在内存中存储整个文件,避免了大文件导致的 内存溢出 (OOM)
* 适用于 几百 MB、甚至 GB 级别的大文件
*/
const onFetchDownLoad = async () => {
// Step 1: 启动 fetch 并获取 Reader
let response: any = await fetch("./M30T.glb");
const reader = response.body.getReader();
// Step 2: 获取文件总大小 +是转数字
const contentLength = +response.headers.get("Content-Length");
// Step 3: read the data
let receivedLength = 0; // 目前收到了多少字节
let chunks = []; // 接收到的二进制块数组(包括 body)
// 你可以自由实现3
const fileStream = streamsaver.createWriteStream("m30t.glb"); // 创建一个文件可写流
const writer = fileStream.getWriter(); // 获取文件流写入器
while (true) {
//value 是一个 Uint8Array ,表示 接收到的多少字节
// done 是一个布尔值,表示是否读取完毕
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
receivedLength += value.length;
console.log(`Received ${receivedLength} of ${contentLength}`);
const percent = Math.round((receivedLength / contentLength) * 100);
console.log(percent + "%");
fetchProgrss.value = percent;
// 你可以自由实现3
await writer.write(value); //数据逐块写入文件
}
// Step 4: 将块chunks连接成单个 Uint8Array
let chunksAll = new Uint8Array(receivedLength); // (4.1)
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position); // (4.2)
position += chunk.length;
}
// 你可以自由实现1: 解码为字符串
// let result = new TextDecoder("utf-8").decode(chunksAll);
// We're done!
// console.log(20220309231613, result);
// 你可以自由实现2: 将 Uint8Array 转换为 Blob ,进行下载 (占用内存)
// const blob = new Blob([chunksAll], { type: "text/plain" });
// console.log("blob", blob);
// // 创建a标签下载blob
// const a = document.createElement("a");
// a.href = URL.createObjectURL(blob);
// a.download = "m30t.glb";
// a.click();
// 你可以自由实现3: 使用 streamSaver+ fetch 进行下载(不占用浏览器内存,直接写入文件)
// pnpm install streamsaver
await writer.close(); // 关闭文件写入流 触发浏览器弹出文件保存对话框。
};
const onXhrDownLoad = () => {
const xhr = new XMLHttpRequest();
xhr.open("GET", "./M30T.glb");
// 设置 responseType 为 blob,确保正确接收二进制数据
xhr.responseType = "blob";
// https://developer.mozilla.org/zh-CN/docs/Web/API/ProgressEvent
xhr.onprogress = (progress) => {
console.log("progress", progress);
const percent = Math.round((progress.loaded / progress.total) * 100);
xhrProgrss.value = percent;
console.log(percent + "%");
};
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 直接使用 xhr.response(blob)创建下载链接
const url = URL.createObjectURL(xhr.response);
const a = document.createElement("a");
a.href = url;
a.download = "M30T.glb";
document.body.appendChild(a); // 兼容 Safari
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log("请求成功", xhr.response);
} else {
console.error("请求失败");
}
}
};
xhr.send();
};
/**内部实现和xhr一样 */
const onAxiosDownLoad = () => {
// your code...
axios({
url: "./M30T.glb",
method: "get",
responseType: "blob",
onDownloadProgress(progress: any) {
const step = Math.round((progress.loaded / progress.total) * 100);
console.log(step + "%");
axiosProgrss.value = step;
},
}).then((res: any) => {
console.log("res", res);
// 直接使用 res创建下载链接
const url = URL.createObjectURL(res.data);
const a = document.createElement("a");
a.href = url;
a.download = "M30T.glb";
document.body.appendChild(a); // 兼容 Safari
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
};
</script>
<style scoped></style>
五、如何去实现监控请求发生的进度
xhr
upload
对象progress
事件。
upload对象是xhr实例中的属性,它用于表示上传的进程,而同时这个对象可以用来添加事件监听器,监听progress事件,就能获得我们需要的信息。
javascript
// 监控请求发送进度
//https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestUpload/progress_event
xhr.upload.addEventListener('progress', (e) => {
// loaded属性代表已经传输的数据量,total代表需要传输的总数据量。
// 单位是字节
console.log(e.loaded, e.total)
console.log( (e.loaded / e.total * 100).toFixed(2) + '%' )
})
progress事件是在数据传输过程中周期性触发,并不是每传输一个数据包触发一次,所以并非100%实时,但这足够向用户反映进度。
axios
javascript
axios({
url: "/api/posts",
method: "post",
data: {
userId: "333",
title: "111",
body: "222",
},
headers: {
"Content-Type": "application/json",
},
onUploadProgress: (progressEvent: any) => {
console.log("!111", progressEvent);
if (progressEvent.lengthComputable) {
//属性lengthComputable主要表明总共需要完成的工作量和已经完成的工作是否可以被测量
//如果lengthComputable为false,就获取不到progressEvent.total和progressEvent.loaded
axoisUpLoadProgress.value =
(progressEvent.loaded / progressEvent.total) * 100; //实时获取上传进度
}
},