How to Use the ReadableStream Body Object from Fetch Responses

Daniel Pericich
4 min readFeb 22, 2024
Photo by marcos mayer on Unsplash

If you have tried to pull data into a JavaScript front-end project, you have likely interacted with the fetch API and promise objects. These tools allow developers to interact with external APIs and retrieve and post data for users. Fetch and promises make code cleaner and easier to read, but they are not as intuitive as the old XMLHttpRequest methods. To master fetch and promises to retrieve data, we must understand how to use the ReadableStream body object returned by fetch responses.

The JavaScript’s fetch API

Before we talk about what the ReadableStream object is, let’s discuss the JavaScript fetch API. This API was added to JavaScript to make talking to web servers simpler and more efficient.

The precursor to fetch was a callback-based tool called XMLHttpRequest. This tool required developers to establish connections to web servers before requesting data and finally closing the connections. It was intuitive as each step of opening a connection, interacting with the server, and closing the connection laid bare what you were doing. However, the repeated actions are tedious, and the code would become jumbled when multiple actions or servers were involved.

Figure 1. Callback Hell caused by nesting code similar to nested XMLHttpRequest calls.

JavaScript maintainers solved these issues by introducing the asynchronous promise-based fetch API. The asynchronous nature of fetch allows users to start processes without blocking the rest of the JavaScript code.

This non-blocking functionality is great for user experience. While asynchronous actions were present with XMLHttpRequest methods, fetch’s syntax makes working with these actions more straightforward.

With fetch, you designate a resource to interact with and an action to perform and receive a promise object. This promise object defaults to pending while the server is fulfilling a request. Once the request is complete, the promise state updates to fulfilled if the action was successful and rejected if it fails:

Figure 2. The lifecycle of a fetch promise.

Every returned fetch promise has a status and a value. For pending promises, the status is pending and the value is undefined. A successful promise will have a status of fulfilled and an object value and a failed promise will have a status of rejected and an error value.

Here we see the status and value of a successful call to Github’s API:

Figure 3. A fetch call to Github for repos from Google and the resulting Promise object.

Fetch promise objects have many attributes including the HTTP response status, the URL, the headers, and most importantly the body. This call is a GET request to return a list of repositories for the Github user Google.

We can see that we received a body value of ReadableStream instead of an array of JSON objects. How do we get the data we need from this ReadableStream object?

What is the ReadableStream Object Returned by fetch Calls?

While the other attributes returned in the fetch promise are useful, we make these GET calls because we want the record data. The data we get in our promise is stored on the body key in an object known as a ReadableStream. This object is flexible and allows fetch to return many different data types.

To access this data, we need to understand the .then() promise method. Promises are powerful objects because they promise to return data at some point, but won’t block the rest of your program while they work. When they finally retrieve your data, you are given a promise object, not a simple array or object.

To turn this promise object into something useful, call .then() on the promise. This method has one argument, the value of the object, and lets you manipulate it in a callback method. In this callback, you can pass the promise value to a part of your program, perform actions on it, or even get another promise back.

When we work with the ReadableStream object, we have several different methods to transform the object into a more conventional object. You could call .blob() to get a blob object, .json() to get JSON, or .text() to get a text value.

It’s important to remember these methods are asynchronous, and thus return a new promise. To access your transformed data you chain another .then() on your fetch call. This second .then() will receive the final data argument you need for your code.

Figure 4. Calling .then() and manipulating our ReadableStream data into useable JSON data

One final note on ReadableSteam objects is they are consumable. This means once you call them with a data method like .text() or .json(), you cannot access them again. Luckily, you can make copies with methods like .clone() to access the value multiple times.

--

--

Daniel Pericich

Former Big Beer Engineer turned Full Stack Software Engineer