异步函数非常适合这样的 fetch()操作。可以通过使用 async/await 将上面的递归调用打平:
fetch('https://fetch.spec.whatwg.org/') .then((response) => response.body) .then(async function(body) {
let reader = body.getReader();
while(true) {
let { value, done } = await reader.read();
if (done) {
break;
}
console.log(value);
}
});
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...
另外,read()方法也可以真接封装到 Iterable 接口中。因此就可以在 for-await-of 循环中方 便地实现这种转换:
fetch('https://fetch.spec.whatwg.org/') .then((response) => response.body) .then(async function(body) {
let reader = body.getReader();
let asyncIterable = {
[Symbol.asyncIterator]() {
return {
next() {
return reader.read();
}
}; }
};
for await (chunk of asyncIterable) {
console.log(chunk);
}
});
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...
通过将异步逻辑包装到一个生成器函数中,还可以进一步简化代码。而且,这个实现通过支持只读 取部分流也变得更稳健。如果流因为耗尽或错误而终止,读取器会释放锁,以允许不同的流读取器继续 操作:
async function* streamGenerator(stream) {
const reader = stream.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
yield value;
}
} finally {
reader.releaseLock();
} }
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then(async function(body) {
for await (chunk of streamGenerator(body)) { console.log(chunk);
} });
要将 Uint8Array 转换为可读文本,可以将缓冲区传给 TextDecoder,返回转换后的值。通过设
置 stream: true,可以将之前的缓冲区保留在内存,从而让跨越两个块的内容能够被正确解码:
在这些例子中,当读取完 Uint8Array 块之后,浏览器会将其标记为可以被垃圾回收。对于需要在 不连续的内存中连续检查大量数据的情况,这样可以节省很多内存空间。
缓冲区的大小,以及浏览器是否等待缓冲区被填充后才将其推到流中,要根据 JavaScript 运行时的 实现。浏览器会控制等待分配的缓冲区被填满,同时会尽快将缓冲区数据(有时候可能未填充数据)发 送到流。
不同浏览器中分块大小可能不同,这取决于带宽和网络延迟。此外,浏览器如果决定不等待网络,
也可以将部分填充的缓冲区发送到流。最终,我们的代码要准备好处理以下情况:
不同大小的 Uint8Array 块;
部分填充的 Uint8Array 块;
块到达的时间间隔不确定。
默认情况下,块是以 Uint8Array 格式抵达的。因为块的分割不会考虑编码,所以会出现某些值作
为多字节字符被分散到两个连续块中的情况。手动处理这些情况是很麻烦的,但很多时候可以使用 Encoding API 的可插拔方案。
Fetch API
let decoder = new TextDecoder();
async function* streamGenerator(stream) {
const reader = stream.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
}
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then(async function(body) {
for await (chunk of streamGenerator(body)) { console.log(decoder.decode(chunk, { stream: true }));
} });
// <!doctype html><html lang="en"> ...
// whether a <a data-link-type="dfn" href="#concept-header" ...
// result to <var>rangeValue</var>. ...
// ...
因为可以使用 ReadableStream 创建 Response 对象,所以就可以在读取流之后,将其通过管道 导入另一个流。然后在这个新流上再使用 Body 的方法,如 text()。这样就可以随着流的到达实时检 查和操作流内容。下面的代码展示了这种双流技术:
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then((body) => {
const reader = body.getReader();
// 创建第二个流
return new ReadableStream({
async start(controller) {
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
// 将主体流的块推到第二个流
controller.enqueue(value);
}
} finally {
controller.close();
reader.releaseLock();
}
}
})
})
.then((secondaryStream) => new Response(secondaryStream))
.then(response => response.text())
.then(console.log);
// <!doctype html><html lang="en"><head><meta charset="utf-8"> ...