xhr和fetch的一些区别对比

一、XMLHttpRequest

1.介绍

  • 是一个浏览器内置的构造函数,简称XHR
  • ajaxaxios都是基于XMLHttpRequest封装的

2.发起XHR的步骤

  1. 创建 xhr 对象

  2. 调用 xhr.open() 函数

  3. 调用 xhr.send() 函数

  4. 监听 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; //实时获取上传进度
      }
    },
相关推荐
uhakadotcom几秒前
Vulkan API 入门指南:跨平台、高性能的图形和计算解决方案
后端·面试·github
uhakadotcom7 分钟前
Meta Horizon OS 开发工具:打造更好的 MR/VR 体验
javascript·后端·面试
helbyYoung23 分钟前
【零基础JavaScript入门 | Day7】三大交互案例深度解析|从DOM操作到组件化开发
开发语言·javascript
uhakadotcom42 分钟前
刚刚发布的React 19.1提供了什么新能力?
前端·javascript·面试
uhakadotcom43 分钟前
Rust中的reqwest库:轻松实现HTTP请求
后端·面试·github
uhakadotcom1 小时前
Expo 简介:跨平台移动应用开发的强大工具
前端·javascript·面试
markzzw1 小时前
浏览器插件钱包(一) - 区块链世界的入口
前端·web3·区块链
uhakadotcom1 小时前
Apache APISIX 简介与实践
后端·面试·github
uhakadotcom1 小时前
Kong Gateway 简介与实践
后端·面试·github
夕水1 小时前
终于,我也能够写出一款代码编辑器
前端