Download Response Data as Stream w/ Axios in React App

30,742

Streaming a response from the browser is not currently supported :

https://github.com/axios/axios/issues/479

Since we're dealing with XMLHttpRequests in the browser, Axios is limited to the specification set by whatwg. :

Specifically, these are the only supported types :

enum XMLHttpRequestResponseType {
  "",
  "arraybuffer",
  "blob",
  "document",
  "json",
  "text"
};

stream is accepted when setting a responseType in axios, but this is misleading. The adapter is going to be xhr.js implicitly since we are using the browser which relies on XMLHttpRequests. HttpRequests are made on the server-side and will allow axios to use the http.js adapter. THEN you can use stream as a ResponseType with Node.js.

Using the fetch API seems to be the only solution with a ReadableStream as a response body type.

Share:
30,742
Torc
Author by

Torc

Updated on March 17, 2022

Comments

  • Torc
    Torc about 2 years

    Issue

    I need to download query results from an endpoint by streaming results to a CSV file. This is in an effort to support enormous ResultSets being sent through the browser at one time.

    Is there a way to accomplish this using Axios in the context of a React App?

    I have seen fetch() and know that it has the following characteristics:

    • returns ReadableStream
    • Is NOT supported by IE11
    • Does NOT allow for intercepting requests
    • The status of a response relates to the request itself, not the HTTP status
      • This means that the only way to receive an error would be to have something go wrong with the stream ending prematurely
      • This definitely won't work for me since I have custom error-handling related to user permissions

    Besides the ReadableStream response type, the rest of the characteristics listed are not permissible. I will need to support IE11 and allow for intercepting requests / reading the HTTP status to determine how to handle the traffic.

    Example with fetch:

          // The promise returned by `fetch` rejects if the fetch was unable to make HTTP-request
          //  e.g. network problems, or there’s no such site.
          // Abnormal HTTP-statuses, such as 404 or 500 do not cause an error.
          const results = await fetch(`${URL}/data`, {
            method: 'post', // HTTP POST to send query to server
            headers: {
              Accept: 'application/json, text/plain, */*', // indicates which files we are able to understand
              'Content-Type': 'application/json', // indicates what the server actually sent
            },
            body: JSON.stringify(query), // server is expecting JSON
            credentials: 'include', // sends the JSESSIONID cookie with the address
          }).then(res => res.json()) // turn the ReadableStream response back into JSON
            .then((res) => {
              if (res.ok) {
                // boolean, true if the HTTP status code is 200-299.
                console.log('response.ok!');
              } else if (res.status === 401) {
                throw Error(`You are not authenticated. Please login.`);
              } else if (res.status === 403) {
                throw Error(`You are not authorized to access this data.`);
              } else {
                throw Error(`Request rejected with status ${res.status}`);
              }
            })
            .catch((error) => {
              // catches error case and if fetch itself rejects
              error.response = {
                status: 0,
                statusText:
                  'Cannot connect. Please make sure you are connected to internet.',
              };
              throw error;
            });
    
          console.log(results);
    

    Example with axios (not streaming)

    Axios instance

    import ...
    const Api = axios.create({
      baseURL: `${URL}`,
      withCredentials: true,
    });
    
    // attach interceptors to requests and responses
    // these are defined elsewhere and imported
    Api.interceptors.request.use((request) => requestHandler(request));
    Api.interceptors.response.use((response) => successHandler(response), (error) => errorHandler(error));
    
    export default Api;
    

    Axios request

    const query = {"selections":{"TABLE_A":["COLUMN1"]},"filters":[{"predicates":[]}],"joins":[],"sorts":[],"limit":100,"offset":0}
    const response = await Api.post('/data', query);
    // further transformations to response to get formatted csv results required
    

    Questions about Axios

    • Is it possible to have a ReadableStream in Axios same as fetch?
    • Is streaming in Axios only possible when assuming that it will be supported by Node in a server-side only setting?
      • Sites like this appear to say that using responseType: 'stream' isn't something that can be done in the browser, only with Node.js using fs
    • Is it possible to use fetch or something else in conjunction with Axios?