使用 Stream 流式处理数据,而不是全载入内存里
Warning本文发布于 2024/03/17,内容可能已过时。
场景
都是处理二进制文件,但相较于 Buffer 或是 Blob 这种一次性载入内存的方式,Stream 则是按需加载
在处理大文件时,我们不希望一次性将整个文件载入内存,而是希望能够通过滑动的窗口流式处理数据,这样可以节省内存。配合上管道操作,可以在从读取流到写入流的过程中进行各种处理,如压缩、加解密、转码等
Stream 的 api 基本上在浏览器和 Node 环境都能使用,但也有一些不同的地方。如在浏览器里是没有 pipeline 的,要使用 pipeThrough 方法
读取文件,并压缩
下面的代码使用了 pipeline 函数,它是 Node 里的一个工具函数,用于将多个流连接起来,当其中一个流出错时,会自动关闭所有流。它在管道中对数据进行了 gzip 压缩
import { createReadStream, createWriteStream } from 'node:fs'import { pipeline as _pipeline } from 'node:stream'import { promisify } from 'node:util'import { createGzip } from 'node:zlib'
const pipeline = promisify(_pipeline)
await pipeline(  createReadStream('package.json'),  createGzip(),  createWriteStream('package.json.gz'),)实现文件下载进度的监听
流式传输的一大用处就是可以在下载文件的时候监听进度,但 fetch 尚不支持上传的监听
async function downloadFile(url: string) {  const response = await fetch(url)
  if (response.status >= 400)    throw new Error(`Bad response from server: ${response.status}`)  if (!response.body)    throw new Error('No body')
  const size = Number(response.headers.get('content-length')) || Number.POSITIVE_INFINITY  const reader = response.body.getReader()
  const readStream = new ReadableStream({    start(controller) {      let received = 0      read()
      async function read() {        const { done, value } = await reader.read()        if (done) {          controller.close()          return        }
        received += value.byteLength        const parsed = Math.floor((received / size) * 100)
        // 或者是其他的报告进度方式        console.log(`Received ${parsed}% of the file`)        controller.enqueue(value)        read()      }    },  })
  return new Response(readStream)}