这段时间做的ai-qa平台,遇到了一个业务需求,就是类gpt的post流式响应:通过post问题到后端,后端采用流式响应,即一段一段的返回数据,我再进行样式的渲染。在此我却遇到了问题,axios没法对post流式响应。
相反原生的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
Axios
是基于Promise
的网络请求库,在node端使用nodejs自带的http模块,在浏览器端采用的是XMLHttpRequests。它的功能包括但不仅限于拦截请求和响应,自动转化JSON数据以及取消请求。
更多内容可以直接查看官网。
这里主要是关于什么是XHR(XMLHttpRequests):
XHR是古早的浏览器内建对象,虽然名字里有XML,它不仅仅能够操作XML格式的数据,其他数据也能够操作,例如图片,文档等等。但随着更新的Fetch的出现,XHR渐渐消失,唯一留下的它的理由估计就是为了兼容旧浏览器,适配旧脚本,以及做到跟踪上传进度(Fetch做不到)。
XMLHttpRequest 有两种执行模式:同步(synchronous)和异步(asynchronous)
先来看看最常使用的异步:
1. 创建 XMLHttpRequest
1let xhr = new XMLHttpRequest();
2
2. 初始化它
1xhr.open(method, URL, [async, user, password]);
2
3. 发送请求
1xhr.send([body]);
2
4. 监听xhr事件获取响应
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 仅在上传时触发它们:
示例:
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是一个现代通用的JS网络请求方法
它的优点在于:
但它依旧有缺点:
接下来让我们看看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模块,包括: