WebGL Tutorial
and more

fetch函数

撰写时间:2024-12-25

修订时间:2025-02-27

fetch函数用于加载特定资源,返回一个注入了Response实例的Promise对象。

Response基本属性

fetch('examples/fetch/text.txt') .then(response => { pc.log(response); [ 'status', 'statusText', 'ok', 'type', 'url', 'redirected', 'body', 'bodyUsed' ].forEach(propName => { pc.log(`${propName}: ${response[propName]}`); }); pc.log(response.headers); let entries = response.headers; for (let [key, value] of entries) { pc.log(` ${key}: ${value}`); } });

其中,body属性所返回的ReadableStream,可参见Stream一节。

资源种类

fetch('examples/fetch/text.txt') .then(response => { if (response.ok) { return response.text(); } }) .then(text => pc.log(text));

由于要加载的资源可能不存在,因此必要时可检查responseok属性。但许多时候,如果我们确信要加载的特定资源必然存在,则此步往往可以省略。

responsetext方法返回一个注入文本的Promise对象,因此可用于Promise串联。下面集中列出了Response所支持的加载各类资源的方法:

arrayBuffer
返回ArrayBuffer实例。
blob
返回Blob实例。
bytes
返回Uint8Array实例。
formData
返回FormData实例。
json
返回JSON实例。
text
返回文本。

加载各类资源

加载文本文件

fetch('examples/fetch/text.txt') .then(response => response.text()) .then(text => pc.log(text));

Responsetext方法返回注入文本内容的Promise

加载了一个名为text.txt的文件,并打印出了加载文件后所提取出的文本的内容。

Bytes

加载文本文件,显示字节

fetch('examples/fetch/text.txt') .then(response => response.bytes()) .then(bytes => pc.log(bytes)) .catch(e => pc.showException(e.message));

Responsebytes方法返回注入Uint8ArrayPromise

各浏览器对bytes方法的支持因高低版本而有异。因此上面的代码在低版本的浏览器中运行时,可能会抛出异常。若是这种情况,最后一行代码确保捕获异常后供PageConsole输出异常信息。

我简直不敢相信自己的眼睛,本章最初撰写时间为2024年12月25日,当时Chrome尚未支持bytes方法。但仅在2025年1月24日,Chrome升级到132版本后就开始支持该方法了。因此,尽可放心调用该函数了。由衷感谢这些大厂的及时技术更新,让开发人员不再左右为难。

与上节相比,都是加载了同一个文本文件,但为何这次显示数字而不是文本?难道文本文件中存储的不是字符串吗?

文本文件的本质

令我们想不到的是,就连日常使用得最多、司空见惯的文本文件,都暗藏众多玄机。

一句话,文本文件中保存的不是字符串,而是字符串经编码后的编码值

fetch返回被注入的ResponseResponsebody属性,将所加载文件的最原始的数据,存储在一个ReadableStream,也即一个可读取数据的流中。

fetch('examples/fetch/text.txt') .then(response => response.body) .then(readableStream => { let writableStream = new WritableStream({ write(chunk) { pc.log(`Data:\n ${chunk}\nare read from the stream.`); } }); readableStream.pipeTo(writableStream); });

上面的代码,将读取到的原始数据,通过管道操作,原封不动地传输到一个只负责打印数值的WritableStream(也即可改写的流)中,我们得以看到原汁原味的结果。(具体细节,请参见Stream一章,这里只需知道该代码可读出原始数据就行了。)

我是在NetBeans中创建text.txt文件的,其默认使用了UTF-8的编码,因此,在保存文件时,NetBeans自动将相应字符串的UTF-8编码值保存进文件中。而当我们通过流的方式来读取出来时,将直接显示这些编码值,而不是将这些编码值解码后所得到的字符串。

而如果我们使用任何一款支持UTF-8编码的文本编辑器打开上述文件时,文本编辑器默默地将这些UTF-8编码解码为我们看得懂的字符串。而上一节中的response.text()所干的就是同样的解码工作。

因此,Responsebody属性存储原始编码值数据,而其text方法将原始编码值数据解码为文本格式,bytes方法将原始编码值数据解码为Uint8Array格式。

故此,文本文件存储的是按诸如ASCII, UTF-8等特定编码格式而编码后的编码值,而非我们想当然的字符串。

文本文件与二进制文件的异同

文本文件与二进制文件相同的地方在于,它们都是以字节为单位存储了整数的数值。

它们不同的地方在于:

  • 存储文件时的编码方式不同。文本文件按诸如ASCII, UTF-8, UTF-16, UTF-32, GB 2312等方式来编码。而二进制文件则按更多的方式来编码,如.png编码方式、.exe编码方式、.wasm编码方式等等。
  • 文本文件的编码值一般可以还原为可打印字符;而二进制文件的编码值无需考虑是否为可打印字符,它可以存储为任意字节。因此,若用文本编辑器按文本进行解码来打开文件,则因被解码为不可打印的字符而看到一大堆乱码
  • 乱码是特定解码方式下的无效字符,但并不等于乱码是无效数据。使用何种方式来编码,就应使用相对应的解码来还原它,就不会再出现乱码。一般通过各类文件的文件扩展名来提供默认解码的依据。

作为例子,下面是一个多信息文本文件的内容:

fetch('examples/fetch/text.rtf') .then(response => response.text()) .then(text => pc.log(text));

尽管它也是一个文本文件,但还以可打印文本的方式,存储了文本字体、段落、颜色等其他额外信息。这样,当使用支持这种格式的文本编辑器,如Mac OS X下的文本编辑应用软件等,来打开该文件时,则可看到并编辑丰富多彩的文本格式。可以说,它就是一个小型的.doc文件。

Response各种方法的意义

综上,Responsebody存储了原始的字节数据,而其诸如text, bytes, arrayBuffer等方法,不过是应用了不同的解码方式后所得到的不同结果而已。

ArrayBuffer

ResponsearrayBuffer方法返回注入ArrayBufferPromise

运行下面代码:

// 1. new Float32Array([0.1, 0.2, 0.3]) let buffer = new ArrayBuffer(12); let dataView = new DataView(buffer); dataView.setFloat32(0 * Float32Array.BYTES_PER_ELEMENT, 0.1, true); dataView.setFloat32(1 * Float32Array.BYTES_PER_ELEMENT, 0.2, true); dataView.setFloat32(2 * Float32Array.BYTES_PER_ELEMENT, 0.3, true); // 2. create an URL let tarr = new Uint8Array(buffer); let blob = new Blob([tarr], {type: "application/octet-stream"}); let urlStr = URL.createObjectURL(blob); // 3. fetch fetch(urlStr) .then(response => response.arrayBuffer()) .then(arrayBuffer => new Float32Array(arrayBuffer)) .then(tarr => pc.log(tarr)); fetch(urlStr) .then(response => response.bytes()) .then(bytes => pc.log(bytes)) .catch(e => pc.showException(e.message)); // 4. free resources URL.revokeObjectURL(urlStr);

第一步,先创建一个Float32Array对象,并将其值初始化为[0.1, 0.2, 0.3]。

第二步,将其内容创建为一个URL。该URL可视为一个二进制文件。

第三步,加载该URL,解码为ArrayBuffer,并以Float32Array的视图来显示该数据。其值为[0.10, 0.20, 0.30],与我们在第一步中所使用的数值一致。接着,再次加载该资源,这次使用Uint8Array的方式来查看,这就是二进制文件的原始数值。

第四步,释放资源。

上面的第三步也可改写为使用流的方式来查看:

const {StreamUtils} = await import('/js/esm/StreamUtils.js'); // 1. new Float32Array([0.1, 0.2, 0.3]) let buffer = new ArrayBuffer(12); let dataView = new DataView(buffer); dataView.setFloat32(0 * Float32Array.BYTES_PER_ELEMENT, 0.1, true); dataView.setFloat32(1 * Float32Array.BYTES_PER_ELEMENT, 0.2, true); dataView.setFloat32(2 * Float32Array.BYTES_PER_ELEMENT, 0.3, true); // 2. create an URL let tarr = new Uint8Array(buffer); let blob = new Blob([tarr], {type: "application/octet-stream"}); let urlStr = URL.createObjectURL(blob); // 3. fetch let readableStream = await fetch(urlStr) .then(response => response.body); StreamUtils.ReadAllChunks(readableStream).then(chunk => { pc.log(chunk); }); // 4. free resources URL.revokeObjectURL(urlStr);

具体参见ArrayBuffer一章。

Blob

Responseblob方法返回注入BlobPromise

fetch('examples/fetch/text.txt') .then(response => response.blob()) .then(blob => { pc.log(blob); return blob.bytes(); }) .then(bytes => pc.log(bytes)) .catch(e => pc.showException(e.message));

Blob的方法中,其text, arrayBuffer, bytes等方法与Response相对应的方法一样。此外,Blobstream方法还可像Response.body一样,返回一个ReadableStream实例。

Blob更大的价值在于,正如上面所见,可以将其作为参数传给URLcreateObjectURL方法,从而创建出一个可供其他对象引用的URL,这是Response获取资源的其他方法都不具备的优势。

JSON

Responsejson方法返回注入JSON对象的Promise

fetch('examples/fetch/demo.json') .then(response => response.json()) .then(json => pc.log(json));

参考资源

Specifications

  1. Fetch Living Standard

Others

  1. JS 监听用户页面访问&页面关闭操作并进行数据上报
  2. HTTP
  3. Response
  4. caniuse API
  5. caniuse components