YYGod0120
XHRvsFetch
Categories: Technology
2024-03-17

前言

这段时间做的ai-qa平台,遇到了一个业务需求,就是类gpt的post流式响应:通过post问题到后端,后端采用流式响应,即一段一段的返回数据,我再进行样式的渲染。 在此我却遇到了问题,axios没法对post流式响应。

github上的closed-issue

相反原生的Fetch却能做到post流式处理。

1//核心代码
2async function run() {
3  aborter.abort(); // cancel previous request
4  outputEl.innerText = "";
5  aborter = new AbortController();
6  const prompt = new FormData(formEl).get("prompt");
7  try {
8    const response = await fetch("http://192.168.223.26:5000/chain", {
9      signal: aborter.signal,
10      method: "POST",
11      headers: { "Content-Type": "application/json" },
12      body: JSON.stringify({
13        prompt,
14      }),
15    });
16    const reader = response.body.getReader();
17    const decoder = new TextDecoder();
18    while (true) {
19      const { done, value } = await reader.read();
20      if (done) {
21        break;
22      }
23      const decoded = decoder.decode(value, { stream: true });
24      console.log(decoded);
25      outputEl.innerText += decoded;
26    }
27  } catch (err) {
28    console.error(err);
29  }
30}
31

于是我去查了查相关资料,发现Axios好像有些地方真不如Fetch

XHR(XMLHttpRequests)

Axios是基于Promise的网络请求库,在node端使用nodejs自带的http模块,在浏览器端采用的是XMLHttpRequests。它的功能包括但不仅限于拦截请求和响应,自动转化JSON数据以及取消请求。

更多内容可以直接查看官网

这里主要是关于什么是XHR(XMLHttpRequests):

XHR是古早的浏览器内建对象,虽然名字里有XML,它不仅仅能够操作XML格式的数据,其他数据也能够操作,例如图片,文档等等。 但随着更新的Fetch的出现,XHR渐渐消失,唯一留下的它的理由估计就是为了兼容旧浏览器,适配旧脚本,以及做到跟踪上传进度(Fetch做不到)。

XMLHttpRequests基础

XMLHttpRequest 有两种执行模式:同步(synchronous)和异步(asynchronous)

先来看看最常使用的异步:

1. 创建 XMLHttpRequest

1let xhr = new XMLHttpRequest();
2

2. 初始化它

1xhr.open(method, URL, [async, user, password]);
2
  • method —— HTTP 方法。通常是 "GET" 或 "POST"。
  • URL —— 要请求的 URL,通常是一个字符串,也可以是 URL 对象。
  • async —— 如果显式地设置为 false,那么请求将会以同步的方式处理,我们稍后会讲到它。
  • user,password —— HTTP 基本身份验证(如果需要的话)的登录名和密码。

3. 发送请求

1xhr.send([body]);
2

4. 监听xhr事件获取响应

  • load —— 当请求完成(即使 HTTP 状态为 400 或 500 等),并且响应已完全下载。
  • error —— 当无法发出请求,例如网络中断或者无效的 URL。
  • progress —— 在下载响应期间定期触发,报告已经下载了多少。
1xhr.onload = function () {
2  alert(`Loaded: ${xhr.status} ${xhr.response}`);
3};
4
5xhr.onerror = function () {
6  // 仅在根本无法发出请求时触发
7  alert(`Network Error`);
8};
9
10xhr.onprogress = function (event) {
11  // 定期触发
12  // event.loaded —— 已经下载了多少字节
13  // event.lengthComputable = true,当服务器发送了 Content-Length header 时
14  // event.total —— 总字节数(如果 lengthComputable 为 true)
15  alert(`Received ${event.loaded} of ${event.total}`);
16};
17

关于XHR的Get请求的典型代码(估计现在也用不上了):

1let xhr = new XMLHttpRequest();
2
3xhr.open("GET", "/my/url");
4
5xhr.send();
6
7xhr.onload = function () {
8  if (xhr.status != 200) {
9    // HTTP error?
10    // 处理 error
11    alert("Error: " + xhr.status);
12    return;
13  }
14
15  // 获取来自 xhr.response 的响应
16};
17
18xhr.onprogress = function (event) {
19  // 报告进度
20  alert(`Loaded ${event.loaded} of ${event.total}`);
21};
22
23xhr.onerror = function () {
24  // 处理非 HTTP error(例如网络中断)
25};
26

上传进度

前面提到过,XML可以做到Fetch做不到的跟踪上传进度——xhr.upload

它会生成事件,类似于 xhr,但是 xhr.upload 仅在上传时触发它们:

  • loadstart —— 上传开始。
  • progress —— 上传期间定期触发。
  • abort —— 上传中止。
  • error —— 非 HTTP 错误。
  • load —— 上传成功完成。
  • timeout —— 上传超时(如果设置了 timeout 属性)。
  • loadend —— 上传完成,无论成功还是 error。

示例:

1xhr.upload.onprogress = function (event) {
2  alert(`Uploaded ${event.loaded} of ${event.total} bytes`);
3};
4
5xhr.upload.onload = function () {
6  alert(`Upload finished successfully.`);
7};
8
9xhr.upload.onerror = function () {
10  alert(`Error during the upload: ${xhr.status}`);
11};
12

Fetch

Fetch是一个现代通用的JS网络请求方法

它的优点在于:

  • 支持async/await
  • 写法简单,api易上手
  • 脱离了XHR,是ES规范里的实现方法

但它依旧有缺点:

  • 浏览器不支持,需要polyfill
  • 默认无cookie
  • HTTP错误不会导致Promise返回reject
  • 不支持查看上传进度
  • 不支持超时控制

接下来让我们看看Fetch的相关流程

Fetch基础

基本语法:

1let promise = fetch(url, [options]);
2

浏览器立即启动请求,并返回一个该调用代码应该用来获取结果的 promise。 获取响应通常需要经过两个阶段。

第一阶段,当服务器发送了响应头(response header),fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析。

在这个阶段,我们可以通过检查响应头的状态来确认请求是否成功,如果fetch没法成功建立,也就是遇到网络问题等网络本身问题,promise就会reject。

因此一切服务器的返回:404or500等等,都不会导致Promise返回reject,从而难以追踪错误。

第二阶段,为了获取 response body,我们需要使用一个其他的方法调用。

Response 提供了多种基于 promise 的方法,来以不同的格式访问 body:

  • response.text() —— 读取 response,并以文本形式返回 response,

  • response.json() —— 将 response 解析为 JSON 格式,

  • response.formData() —— 以 FormData 对象(在 下一章 有解释)的形式返回 response,

  • response.blob() —— 以 Blob(具有类型的二进制数据)形式返回 response,

  • response.arrayBuffer() —— 以 ArrayBuffer(低级别的二进制数据)形式返回 response,

    另外,response.body 是 ReadableStream 对象,它允许你逐块读取 body,GPTpost响应流就是基于此进行流式响应:

1const response = await fetch("http://192.168.223.26:5000/chain", {
2  signal: aborter.signal,
3  method: "POST",
4  headers: { "Content-Type": "application/json" },
5  body: JSON.stringify({
6    prompt,
7  }),
8});
9const reader = response.body.getReader();
10

我们只能选择一种读取 body 的方法。 如果我们已经使用了 response.text() 方法来获取 response,那么如果再用 response.json(),则不会生效,因为 body 内容已经被处理过了。

其他

XML:可扩展标记语言(Extensible Markup Language,XML)是一种标记语言。XML是从标准通用标记语言(SGML)中简化修改出来的。

AJAX: AJAX(Asynchronous JavaScript And XML,异步 JavaScript 和 XML)是一种使用 XMLHttpRequest 技术构建更复杂,动态的网页的编程实践。 其中实现方法有古早的XHR以及新的Fetch。

HTTP模块: Http模块指的是node中Http模块,包括:

  • http.createServer 担当web服务器
  • http.createClient,担当客户端,实现爬虫之类的工作。
© 2023 - 2024
githubYYGod0120