Search code examples
reactjstypescriptfetch

TS2504: Type 'ReadableStream<Uint8Array>' must have a '[Symbol.asyncIterator]()' method that returns an async iterator


I'm trying to use the body of a fetch response as an async iterable in a new CRA React application.

const response = await fetch('example.com');

if (response.ok && response.body) {
  for await (const b of response.body) {
    ...
  }
}

It should be possible according to MDN:

The body read-only property of the Response interface is a ReadableStream of the body contents.

And:

ReadableStream implements the async iterable protocol. This enables asynchronous iteration over the chunks in a stream using the for await...of syntax:

I already set the target to esnext in tsconfig.json according to this answer, but I still get the error.


Solution

  • First note that out of the browsers, only Firefox actually implements async iterable on ReadableStream as listed in the compatibility table here
    Update: as of April 2024, Chrome 124+ also implements async iterable.

    For this reason the official types do not include the method.

    An alternative if running in node, and browser polyfill are available from here

    if the code only runs in Nodejs, then do the type casting directly:

    import { default as stream } from 'node:stream'.
    import type { ReadableStream } from 'node:stream/web'.
    
    const response = await fetch('xxx')
    stream.Readable.fromWeb(response.body as ReadableStream<Uint8Array>)
    

    If the code needs to take into account the browser's runtime environment, you can add a polyfill:

    ReadableStream.prototype[Symbol.asyncIterator] = async function* () {
      const reader = this.getReader()
      try {
        while (true) {
          const {done, value} = await reader.read()
          if (done) return
          yield value
        }
      }
      finally {
        reader.releaseLock()
      }
    }
    

    If you are just running in a browser that implements the feature, adding this makes the compiler happy

    interface ReadableStream<R = any> {
        [Symbol.asyncIterator](): AsyncIterableIterator<R>;
    }