Bootstrap

JavaScript 中的 Stream API 04(转换流 TransformStream 等 API)

Stream API

转换流 API

一个转换流接收所有的分块将其转换为 Uint8Array,上面可读流中的 value 也为该 Uint8Array 格式

TransformStream

TransformStream 是 Web Streams API 的一部分,它允许你通过提供一个转换函数来创建一个可写和可读的流对 → 这个转换函数接收来自上游的数据块(chunks),执行某些操作(如转换、过滤等),然后将结果数据块发送到下游 → 链式管道传输(pipe chain)转换流概念的具体实现

主要用途:

  • 转换数据: 在数据从源流向目的地的过程中对数据进行处理
  • 保持背压: 当下游消费数据的速度跟不上上游生产数据的速度时,能够自动暂停上游的写入操作,直到下游准备好继续接收数据

TransformStream 转换流实例对象的创建: new TransformStream(transformer, writableStrategy, readableStrategy)

  • transformer(可选): 该配置对象下面的每一个方法中都包含一个 controller(TransformStreamDefaultController 实例对象)参数

    • start(controller):

      • 该方法会在 TransformStream 实例对象被构建时来调用
      • 通常用于 TransformStreamDefaultController.equeue 将块压入流的队列中
    • transform(chunk,controller):

      • 当一个数据块(chunk)被写入到 WritableStream 端时,transform 函数会被调用,我们可以在该函数中对 chunk 做一些转换(一些自定义操作,开发者决定)后在通过 controller 中的 enqueue 压入队列

      • 即我们可以理解为一个 chunk 在被写入可写流之前,我们可以在该方法中先对 chunk 进行一些自定义的操作(转换),在将 chunk 压入到对应流的队列中(也可以理解为是一个拦截器)

      • {
         	// -- 如 ↓: 当一个 chunk 被写入到可写流时,就会调用该函数,我们可以该函数对 chunk 进行一些转换等操作,如下示例转换成大小字符
            transform(chunk, controller) {
              controller.enqueue(chunk.toUpperCase()); 
            }
        }
        // 这个转换流可以被用于管道操作中,如将文本数据从一种格式转换为另一种格式
        
    • flush(controller):

      • 当所有数据块都已经被写入到 WritableStream 端时,调用该方法且会关闭可写流
      • 可以理解为当已将所有数据写入后,调用了 writer 中的 close 关闭可写流的写入时触发该函数
  • writableStrategy(可选): 可写流的策略配置对象,具体参数与上面可读流一样,略...

  • readableStrategy(可选): 可读流的策略配置对象,具体参数与上面可读流一样,略...

实例属性: 转换流实例对象上只有两个属性,分别对应 ReadableStream 和 WritableStream 实例对象

  • readable: 返回当前转换流中的 ReadableStream 可读流实例对象
  • writable: 返回当前转换流中的 WritableStream 可写流实例对象

基本使用示例:

  • const transfer = new TransformStream({ // -- 1. 创建一个转换流
        start(controller) {
            console.log("init transform stream")
        },
    
        transform(chunk, controller) { // -- 2. 当一个 chunk 即将被写入到可写流中时,就会触发该方法 → 我们可以在 chunk 写入前在该方法中对 chunk 进行一些转换等操作 → 如下示例
            // -- 如下: 这里先将 chunk 转换成大写(可写流需写入字符),转换成大写后再通过 controller.enqueue 将其压入对应的队列中(使用该新的 chunk 进行写入可写流中)
            const chunkTransformed = chunk.toUpperCase()
            controller.enqueue(chunkTransformed)
        },
    
        flush(controller) {  // -- 3. 当所有数据块都写入完时(即调用 writer.close() 关闭可写流时),触发该方法 → 可在该方法中做一些清除操作等
            console.log("flush")
            controller.terminate() // -- 当流已经全部处理完时,通过该方法来结束流的处理过程(关闭流)
        }
    })
    
  • // -- 4. 通过转换流 TransformStream 实例对象上的 readable 和 writer 属性获取对应的可读流与可写流对象
    const readable = transfer.readable
    const writable = transfer.writable
    
    // -- 5. 通过 readable 和 writable,获取对应的读取器 reader 与写入器 writer → 当然也可以直接通直接获取(如: transfer.readable.getReader),这里为了每一步都清除一些,所以就逐步来获取
    const reader = readable.getReader()
    const writer = writable.getWriter()
    
  • // -- 6. 当 desiredSize 为正数时(无需背压),在里面模拟服务服务器返回流数据进行写入
    writer.ready.then(() => {
        writer.write("Kong")
    
        // -- 通过 setTimeout 默认流数据的请求
        setTimeout(() => writer.write("Xiang"), 200)
        setTimeout(() => writer.write("Hunag"), 400)
        setTimeout(() => writer.write("Xiao"), 600)
        setTimeout(() => {
            writer.write("Kong")
    
            writer.close() // -- 当所有数据都写入完成后,关闭可写流(如果不关闭,可读流在读取是对应的 done 属性将一直为 false,同理也不会触发上面转换流中的 flush 方法)
        }, 800)
    }).catch(err => {
        console.log("写入失败");
    })
    
  • // -- 7. 通过 reader 读取流中所有的数据块
    const read = () => {
        reader.read().then(({ value, done }) => { // -- 读取流中的每一个数据块
            if (done) { // -- 当流中所有数据块都读取完毕后,设置提示并返回(反之,在下面递归读取数据块)
                console.log("Stream readed completed")
                return
            }
    
            console.log(value) // -- 打印当前数据块内容
            read() // -- 递归读取
        })
    }
    read() // -- 开始读取
    
TransformStreamDefaultController

该 API 用于提供对应操作 ReadableStream 和 WritableStream 的方法 → 该方法同样是没有对应的构造函数的,当在构造 TransformStream 实例对象时,会自动创建对应的 TransformStreamDefaultController 实例对象(传递给对应 TransformStream 的行为方法中)

实例属性:

  • desired: 属性返回填充满流内部队列的可读端所需要的大小

实例方法:

  • enqueue: 该方法用于将给定的分块排入流的可读端 → enqueue(chunk)

  • error: 该方法可以向流的两端(可读流和可写流)抛出错误 → 与它的进一步交互都会失败并携带给定的错误信息,并且队列中的任何分块都将被丢弃 → error(reason)

    • // -- 在这个示例中,当一个分块中包含 symbol 时,error() 方法被使用 → 由开发者控制
      case 'symbol':
        controller.error("Cannot send a symbol as a chunk part")
        break
      
  • terminate: 该方法用于关闭两端的流(可读流和可写流) → terminate()

    • 场景: 
      → 正常完成:当转换器处理完所有输入数据,并且没有更多的数据需要发送到下游时
      → 错误处理:如果在转换过程中遇到无法恢复的错误,并且你希望立即停止流的进一步处理
      → 条件终止:基于特定的输入或状态条件,你可能决定提前终止流
      

该 API 具体的用法,在 TransformStream 中进行示例

;