javascript基础从小白到高手系列二百三十四:使用ReadableStream 主体

本文详细介绍了JavaScript中如何使用FetchAPI和ReadableStream进行异步网络数据流处理,包括Uint8Array的使用、数据块的分块和解码、双流技术的应用,以及处理不同大小和填充状态的块的策略。
摘要由CSDN通过智能技术生成

JavaScript 编程逻辑很多时候会将访问网络作为原子操作,比如请求是同时创建和发送的,响应数
据也是以统一的格式一次性暴露出来的。这种约定隐藏了底层的混乱,让涉及网络的代码变得很清晰。
从TCP/IP 角度来看,传输的数据是以分块形式抵达端点的,而且速度受到网速的限制。接收端点
会为此分配内存,并将收到的块写入内存。Fetch API 通过ReadableStream 支持在这些块到达时就实
时读取和操作这些数据。
正如Stream API所定义的,ReadableStream 暴露了getReader()方法,用于产生ReadableStream-
DefaultReader,这个读取器可以用于在数据到达时异步获取数据块。数据流的格式是Uint8Array。
下面的代码调用了读取器的read()方法,把最早可用的块打印了出来:
fetch(‘https://fetch.spec.whatwg.org/’)
.then((response) => response.body)
.then((body) => {
let reader = body.getReader();
console.log(reader); // ReadableStreamDefaultReader {}
reader.read()
.then(console.log);
});
// { value: Uint8Array{}, done: false }
在随着数据流的到来取得整个有效载荷,可以像下面这样递归调用read()方法:
fetch(‘https://fetch.spec.whatwg.org/’)
.then((response) => response.body)
.then((body) => {
let reader = body.getReader();
function processNextChunk({value, done}) {
if (done) {
return;
}
console.log(value);
return reader.read()
.then(processNextChunk);
}
return reader.read()
.then(processNextChunk);
});
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// …
异步函数非常适合这样的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 块之后,浏览器会将其标记为可以被垃圾回收。对于需要在
不连续的内存中连续检查大量数据的情况,这样可以节省很多内存空间。
缓冲区的大小,以及浏览器是否等待缓冲区被填充后才将其推到流中,要根据JavaScript 运行时的
实现。浏览器会控制等待分配的缓冲区被填满,同时会尽快将缓冲区数据(有时候可能未填充数据)发
送到流。
不同浏览器中分块大小可能不同,这取决于带宽和网络延迟。此外,浏览器如果决定不等待网络,
也可以将部分填充的缓冲区发送到流。最终,我们的代码要准备好处理以下情况:
 不同大小的Uint8Array 块;
 部分填充的Uint8Array 块;
 块到达的时间间隔不确定。
默认情况下,块是以Uint8Array 格式抵达的。因为块的分割不会考虑编码,所以会出现某些值作
为多字节字符被分散到两个连续块中的情况。手动处理这些情况是很麻烦的,但很多时候可以使用
Encoding API 的可插拔方案。
要将Uint8Array 转换为可读文本,可以将缓冲区传给TextDecoder,返回转换后的值。通过设
置stream: true,可以将之前的缓冲区保留在内存,从而让跨越两个块的内容能够被正确解码:
let decoder = new TextDecoder();
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(decoder.decode(chunk, { stream: true }));
}
});
// <!doctype html> …
// whether a <a data-link-type=“dfn” href=“#concept-header” …
// result to rangeValue. …
// …
因为可以使用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> …

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: HTML DOM,即HTML文档对象模型,是一种用于访问和操作HTML文档中元素的标准方法。在JavaScript中,可以使用DOM来查找、修改、添加和删除HTML元素。在本教程中,我们将学习如何使用DOM来操作HTML文档的元素。 ### 回答2: JavaScript学习手册十四:HTML DOM——文档元素的操作(二)介绍了文档元素的一些高级操作,包括插入和删除节点、替换节点、复制节点等。下面我将详细介绍这些操作的用法和注意事项。 首先是插入节点,包括appendChild()和insertBefore()方法。appendChild()方法可以将一个节点追加到某个元素的子节点列表的末尾,而insertBefore()方法可将一个节点插入到元素的指定位置。这两个方法都可以接收字符串、元素、文本节点等参数,可以将它们转换为节点后插入。需要注意的是,插入节点后必须重新获取元素才能正确地操作修改后的DOM树。 接下来是删除节点,包括removeChild()和replaceChild()方法。removeChild()方法可以移除指定节点,replaceChild()方法则可将指定节点替换为另一个节点。调用这两个方法也需要重新获取元素。 另外,还有cloneNode()和hasChildNodes()方法。cloneNode()方法可以复制一个节点,包括节点的属性、事件、插入的子节点等,但不包括子节点的引用和事件处理程序。hasChildNodes()方法可判断节点是否包含子节点。 需要注意的是,在使用这些高级DOM操作时,应尽可能减少DOM的操作次数。因为频繁操作DOM可能会导致性能问题,因此应该尽可能缓存和重用DOM元素,同时避免频繁更新DOM。此外,在修改DOM树时,应当小心操作,避免影响网页布局和用户体验。 总之,JavaScript学习手册十四:HTML DOM——文档元素的操作(二)提供了一些高级操作DOM树的方法,可以方便地插入、删除、替换和复制节点。合理地使用这些方法,可以为网页的构建、交互和流畅度提供帮助。 ### 回答3: HTML DOM是JavaScript操作HTML和XML的接口,它将HTML和XML文档表示为具有层次结构的节点树,通过对节点树进行操作,可以实现对文档元素的增删改查。之前我们已经学习了文档元素的遍历和属性的操作,本篇文章将进一步介绍如何对文档元素进行插入、替换和移除操作。 文档元素的插入操作,可以通过createElement()方法创建一个元素节点,然后通过appendChild()将该节点插入到指定的父节点下。还可以使用insertBefore()方法在指定位置插入节点,该方法需要指定要插入的节点和插入位置的参考节点。同时还可以使用insertAdjacentHTML()方法直接插入HTML代码并将其转换为节点。 文档元素的替换操作,可以通过replaceChild()方法实现,该方法需要指定要替换的节点和替换后的新节点。替换操作可以用于更新页面中的元素,比如将旧的图片替换成新的图片等。 文档元素的移除操作,可以通过removeChild()方法实现,该方法需要指定要移除的节点,并将其从父元素节点中删除。还可以使用remove()方法直接将元素节点从页面中删除。 在进行文档元素操作时,需要注意避免对页面结构造成破坏,比如插入重复的元素或删除父元素节点等操作。另外,DOM操作可能会影响页面的性能,因此在实际应用中需要谨慎处理。 总的来说,HTML DOM提供了丰富的接口,可以实现对文档元素的灵活操作,帮助我们更好地控制和管理页面内容。对于Web前端开发者而言,熟练掌握HTML DOM操作技巧是非常重要的一项技能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值