
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
</svg>
<p>JavaScript 编程逻辑很多时候会将访问网络作为原子操作,比如请求是同时创建和发送的,响应数<br /> 据也是以统一的格式一次性暴露出来的。这种约定隐藏了底层的混乱,让涉及网络的代码变得很清晰。<br /> 从TCP/IP 角度来看,传输的数据是以分块形式抵达端点的,而且速度受到网速的限制。接收端点<br /> 会为此分配内存,并将收到的块写入内存。Fetch API 通过ReadableStream 支持在这些块到达时就实<br /> 时读取和操作这些数据。<br /> 正如Stream API所定义的,ReadableStream 暴露了getReader()方法,用于产生ReadableStream-<br /> DefaultReader,这个读取器可以用于在数据到达时异步获取数据块。数据流的格式是Uint8Array。<br /> 下面的代码调用了读取器的read()方法,把最早可用的块打印了出来:<br /> fetch(‘https://fetch.spec.whatwg.org/’)<br /> .then((response) => response.body)<br /> .then((body) => {<br /> let reader = body.getReader();<br /> console.log(reader); // ReadableStreamDefaultReader {}<br /> reader.read()<br /> .then(console.log);<br /> });<br /> // { value: Uint8Array{}, done: false }<br /> 在随着数据流的到来取得整个有效载荷,可以像下面这样递归调用read()方法:<br /> fetch(‘https://fetch.spec.whatwg.org/’)<br /> .then((response) => response.body)<br /> .then((body) => {<br /> let reader = body.getReader();<br /> function processNextChunk({value, done}) {<br /> if (done) {<br /> return;<br /> }<br /> console.log(value);<br /> return reader.read()<br /> .then(processNextChunk);<br /> }<br /> return reader.read()<br /> .then(processNextChunk);<br /> });<br /> // { value: Uint8Array{}, done: false }<br /> // { value: Uint8Array{}, done: false }<br /> // { value: Uint8Array{}, done: false }<br /> // …<br /> 异步函数非常适合这样的fetch()操作。可以通过使用async/await 将上面的递归调用打平:<br /> fetch(‘https://fetch.spec.whatwg.org/’)<br /> .then((response) => response.body)<br /> .then(async function(body) {<br /> let reader = body.getReader();<br /> while(true) {<br /> let { value, done } = await reader.read();<br /> if (done) {<br /> break;<br /> }<br /> console.log(value);<br /> }<br /> });<br /> // { value: Uint8Array{}, done: false }<br /> // { value: Uint8Array{}, done: false }<br /> // { value: Uint8Array{}, done: false }<br /> // …<br /> 另外,read()方法也可以真接封装到Iterable 接口中。因此就可以在for-await-of 循环中方<br /> 便地实现这种转换:<br /> fetch(‘https://fetch.spec.whatwg.org/’)<br /> .then((response) => response.body)<br /> .then(async function(body) {<br /> let reader = body.getReader();<br /> let asyncIterable = {<br /> Symbol.asyncIterator {<br /> return {<br /> next() {<br /> return reader.read();<br /> }<br /> };<br /> }<br /> };<br /> for await (chunk of asyncIterable) {<br /> console.log(chunk);<br /> }<br /> });<br /> // { value: Uint8Array{}, done: false }<br /> // { value: Uint8Array{}, done: false }<br /> // { value: Uint8Array{}, done: false }<br /> // …<br /> 通过将异步逻辑包装到一个生成器函数中,还可以进一步简化代码。而且,这个实现通过支持只读<br /> 取部分流也变得更稳健。如果流因为耗尽或错误而终止,读取器会释放锁,以允许不同的流读取器继续<br /> 操作:<br /> async function* streamGenerator(stream) {<br /> const reader = stream.getReader();<br /> try {<br /> while (true) {<br /> const { value, done } = await reader.read();<br /> if (done) {<br /> break;<br /> }<br /> yield value;<br /> }<br /> } finally {<br /> reader.releaseLock();<br /> }<br /> }<br /> fetch(‘https://fetch.spec.whatwg.org/’)<br /> .then((response) => response.body)<br /> .then(async function(body) {<br /> for await (chunk of streamGenerator(body)) {<br /> console.log(chunk);<br /> }<br /> });<br /> 在这些例子中,当读取完Uint8Array 块之后,浏览器会将其标记为可以被垃圾回收。对于需要在<br /> 不连续的内存中连续检查大量数据的情况,这样可以节省很多内存空间。<br /> 缓冲区的大小,以及浏览器是否等待缓冲区被填充后才将其推到流中,要根据JavaScript 运行时的<br /> 实现。浏览器会控制等待分配的缓冲区被填满,同时会尽快将缓冲区数据(有时候可能未填充数据)发<br /> 送到流。<br /> 不同浏览器中分块大小可能不同,这取决于带宽和网络延迟。此外,浏览器如果决定不等待网络,<br /> 也可以将部分填充的缓冲区发送到流。最终,我们的代码要准备好处理以下情况:<br /> 不同大小的Uint8Array 块;<br /> 部分填充的Uint8Array 块;<br /> 块到达的时间间隔不确定。<br /> 默认情况下,块是以Uint8Array 格式抵达的。因为块的分割不会考虑编码,所以会出现某些值作<br /> 为多字节字符被分散到两个连续块中的情况。手动处理这些情况是很麻烦的,但很多时候可以使用<br /> Encoding API 的可插拔方案。<br /> 要将Uint8Array 转换为可读文本,可以将缓冲区传给TextDecoder,返回转换后的值。通过设<br /> 置stream: true,可以将之前的缓冲区保留在内存,从而让跨越两个块的内容能够被正确解码:<br /> let decoder = new TextDecoder();<br /> async function* streamGenerator(stream) {<br /> const reader = stream.getReader();<br /> try {<br /> while (true) {<br /> const { value, done } = await reader.read();<br /> if (done) {<br /> break;<br /> }<br /> yield value;<br /> }<br /> } finally {<br /> reader.releaseLock();<br /> }<br /> }<br /> fetch(‘https://fetch.spec.whatwg.org/’)<br /> .then((response) => response.body)<br /> .then(async function(body) {<br /> for await (chunk of streamGenerator(body)) {<br /> console.log(decoder.decode(chunk, { stream: true }));<br /> }<br /> });<br /> // <!doctype html> …<br /> // whether a <a data-link-type=“dfn” href=“#concept-header” …<br /> // result to <var>rangeValue</var>. …<br /> // …<br /> 因为可以使用ReadableStream 创建Response 对象,所以就可以在读取流之后,将其通过管道<br /> 导入另一个流。然后在这个新流上再使用Body 的方法,如text()。这样就可以随着流的到达实时检<br /> 查和操作流内容。下面的代码展示了这种双流技术:<br /> fetch(‘https://fetch.spec.whatwg.org/’)<br /> .then((response) => response.body)<br /> .then((body) => {<br /> const reader = body.getReader();<br /> // 创建第二个流<br /> return new ReadableStream({<br /> async start(controller) {<br /> try {<br /> while (true) {<br /> const { value, done } = await reader.read();<br /> if (done) {<br /> break;<br /> }<br /> // 将主体流的块推到第二个流<br /> controller.enqueue(value);<br /> }<br /> } finally {<br /> controller.close();<br /> reader.releaseLock();<br /> }<br /> }<br /> })<br /> })<br /> .then((secondaryStream) => new Response(secondaryStream))<br /> .then(response => response.text())<br /> .then(console.log);<br /> // <!doctype html> …</p>
讯享网

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/179142.html