Stream api with fetch in a react-native app
Asked Answered
G

3

9

I was trying to use Stream api with fetch in react-native app, I implemented with the help of a great example mentioned at jeakearchibald.com . code is something similar to :-

fetch('https://html.spec.whatwg.org/').then(function(response) {
  console.log('response::-', response)
  var reader = response.body.getReader();
  var bytesReceived = 0;

  reader.read().then(function processResult(result) {
    if (result.done) {
      console.log("Fetch complete");
      return;
    }
    bytesReceived += result.value.length;
    console.log(`Received ${bytesReceived} bytes of data so far`);

    return reader.read().then(processResult);
  });
});

Stream api reference is :-

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

But it seems fetch implementation of react-native is little different than of browsers and it is not easy to use Stream in the same way as used on web.

There is already an unresolved issue on react-native for the same https://github.com/facebook/react-native/issues/12912

On web we can access Stream from response.body.getReader(), where response is just normal result retuned from fetch call of stream url, but in react-native there is no way we can access body and hence getReader from response of fetch call.

So to overcome this I tried to use rn-fetch-blob npm package , because it supports Streams, but that to seems to support only from locale file paths because there readStream functions doesn't seems to have support to pass Authorization and other necessary headers, so I tried to use RNFetchBlob.fetch with the remote url and necessary headers and then using readStream method from response but that always returns me there is no stream with the current response.

RNFetchBlob.fetch('GET', 'https://html.spec.whatwg.org/')
      .progress((received, total) => {
        console.log('progress', received / total);
      })
      .then((resp) => {
        // const path = resp.path();
        console.log('resp success:-', resp);
        RNFetchBlob.fs.readStream(path, 'utf8').then((stream) => {
          let data = '';
          stream.open();
          stream.onData((chunk) => {
            data += chunk;
          });
          stream.onEnd(() => {
            console.log('readStream::-', data);
          });
        // });
      })
      .catch((err) => {
        console.log('trackAppointmentStatus::-', err);
      });

I may be doing something wrong in both approaches of mine, so little guidance may help me or someone else in the future. Or I may need to find a way to do it natively with writing a bridge.

Gilford answered 19/5, 2019 at 12:47 Comment(0)
R
11

If you are using React Native, it used to not be possible to do this.

But streaming is now possible with https://github.com/react-native-community/fetch.

This was actually a bug that was never addressed by RN team for a while, and this repo emerged to provide a better fetch that complies with WHATWG Spec

This is a fork of GitHub's fetch polyfill, the fetch implementation React Native currently provides. This project features an alternative fetch implementation directy built on top of React Native's Networking API instead of XMLHttpRequest for performance gains. At the same time, it aims to fill in some gaps of the WHATWG specification for fetch, namely the support for text streaming.

Here's how to use it:

Install

This concise steps are from hours of debugging, and I dont want to waste your time.

$ npm install react-native-fetch-api --save

Now install polyfills:

$ npm install react-native-polyfill-globals

Use the polyfill with fetch:

Add the following code to the top of your app's entry file, index.js, located at the root of your project. Now your new Fetch is available globally.

import { polyfill as polyfillFetch } from 'react-native-polyfill-globals/src/fetch';
polyfill();

Now you can use the stream object like the normal browser fetch. Make sure to specify the option textStreaming true.

fetch('https://jsonplaceholder.typicode.com/todos/1', { reactNative: { textStreaming: true } })
  .then(response => response.body)
  .then(stream => ...)

Hope this helps!

Roxyroy answered 12/9, 2023 at 12:31 Comment(4)
We can also directly auto import polyfill using import 'react-native-polyfill-globals/auto'; in the index.js file instead!Windblown
Do note, if you are on Android, it wont work in debug mode. Can fix it using this answer: https://mcmap.net/q/1315597/-react-native-can-39-t-connect-to-sse-in-androidWindblown
I am getting this error the moment I do the fetch call - [ReferenceError: Property 'TextEncoder' doesn't exist]Colicroot
@Colicroot Try to also polyfill TextEncoder. I used import encoding from "text-encoding"; And then const decoder = new encoding.TextDecoder("utf-8"); to decode values from my response.body.getReader().read()Cider
C
0

For those who are using Expo.

The correct way to polyfill in Expo is:

Create an index.js file and make your polyfill the first import:

import 'polyfill' // ('react-native-polyfill-globals/auto' or 'web-streams-polyfill' or whatever)

import 'expo-router/entry'

Then change the main field in the package.json to point to the "main": "./index".

As stated in this discussion

For me personally worked the following setup.

I've created index.js file, modified my package.json to point to the "main": "./index".

import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
import { ReadableStream } from "web-streams-polyfill";
import { fetch, Headers, Request, Response } from "react-native-fetch-api";

polyfillGlobal("ReadableStream", () => ReadableStream);
polyfillGlobal("TextDecoder", () => TextEncoder);
polyfillGlobal(
  "fetch",
  () =>
    (...args: any[]) =>
      fetch(args[0], { ...args[1], reactNative: { textStreaming: true } }),
);
polyfillGlobal("Headers", () => Headers);
polyfillGlobal("Request", () => Request);
polyfillGlobal("Response", () => Response);

import "expo-router/entry";

And then finally I was able to make fetch requests and body was working ReadableStream.

Cider answered 14/9 at 7:46 Comment(0)
C
0

🔗 Source: https://github.com/facebook/react-native/issues/27741#issuecomment-2362901032


🚀 Inspired by the best answer, I came up with this:

  1. Run yarn add react-native-fetch-api react-native-polyfill-globals web-streams-polyfill
  2. If you use TextDecoder, also run yarn add text-encoding
  3. Copy paste the following code snippet in your index.js
// This file was adapted from: https://github.com/facebook/react-native/issues/27741#issuecomment-2362901032

import { polyfill } from 'react-native-polyfill-globals/src/fetch';
polyfill();

// 👇 https://mcmap.net/q/1315598/-struggling-to-fix-quot-referenceerror-property-39-readablestream-39-doesn-39-t-exist-quot
import { ReadableStream as ReadableStreamPolyfill } from 'web-streams-polyfill/dist/ponyfill';
// @ts-ignore
globalThis.ReadableStream = ReadableStreamPolyfill;

// https://mcmap.net/q/1315599/-referenceerror-property-39-textencoder-39-doesn-39-t-exist
import 'text-encoding';

// Needed for TypeScript:
declare global {
  interface RequestInit {
    /**
     * @description Polyfilled to enable text ReadableStream for React Native:
     * @link https://github.com/facebook/react-native/issues/27741#issuecomment-2362901032
     */
    reactNative?: {
      textStreaming: boolean;
    };
  }
}
  1. Use readable stream:
fetch('https://jsonplaceholder.typicode.com/todos/1', { 
   // @ts-ignore https://github.com/facebook/react-native/issues/27741#issuecomment-2362901032
   reactNative: { textStreaming: true } 
}).then(response => { 
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
       .... 
})

Chase answered 20/9 at 6:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.