360

How may I get information from a ReadableStream object?

I am using the Fetch API and I don't see this to be clear from the documentation.

The body is being returned as a ReadableStream and I would simply like to access a property within this stream. Under Response in the browser dev tools, I appear to have this information organised into properties, in the form of a JavaScript object.

fetch('http://192.168.5.6:2000/api/car', obj)
    .then((res) => {
        if(!res.ok) {
            console.log("Failure:" + res.statusText);
            throw new Error('HTTP ' + res.status);
        } else {
            console.log("Success :" + res.statusText);
            return res.body // what gives?
        }
    })
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
noob
  • 5,954
  • 6
  • 20
  • 32
  • 3
    @FrancescoPezzella Thanks for the response. I have tried `response.Body.json()` , but I am getting _italic_ TypeError: Cannot read property 'json' of undefined _italic_ . Is this because the bodyUsed property is also set to false? However I can view this body under the response tab in browser developer tools. There is an error message which I'd like to retrieve. – noob Nov 03 '16 at 10:35
  • So your issue is purely related to the error 400 condition? What happens if you change the handler to `console.log(res.json());`? Do you see the data you are expecting? – Ashley 'CptLemming' Wilson Nov 03 '16 at 16:32
  • @noob Are you trying to read the response as a stream if `res.status == 200`? – guest271314 Dec 18 '16 at 20:11
  • Is it just me or that documentation is *plain* wrong? I did fix it with the solutions on this answers though. – Lucio May 25 '18 at 19:15
  • I know it has been a while but for the sake of keeping stackoverflow great, please just accept the right answer. The one with over 200 upvotes. – xaddict Dec 24 '20 at 12:57

11 Answers11

461

In order to access the data from a ReadableStream you need to call one of the conversion methods (docs available here).

As an example:

fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(function(response) {
    // The response is a Response instance.
    // You parse the data into a useable format using `.json()`
    return response.json();
  }).then(function(data) {
    // `data` is the parsed version of the JSON returned from the above endpoint.
    console.log(data);  // { "userId": 1, "id": 1, "title": "...", "body": "..." }
  });

EDIT: If your data return type is not JSON or you don't want JSON then use text()

As an example:

fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(function(response) {
    return response.text();
  }).then(function(data) {
    console.log(data); // this will be a string
  });
starball
  • 20,030
  • 7
  • 43
  • 238
  • 1
    Thanks for the response. I have tried this and am still getting the same error where res.body is undefined. I am able to retrieve the status however in first then() function with res.status. It seems that only the body is a ReadableStream object. It does seem to have a property locked, which is set to true? – noob Nov 03 '16 at 14:41
  • Where are you trying to access `res.body` (this isn't part of my example)? Can you share some sample code in your original question to make it clearer where your problem might be. – Ashley 'CptLemming' Wilson Nov 03 '16 at 15:28
  • 1
    I tried accessing res.body from the json response that was returned in first .then() function. I have added a sample to my original question for more clarity. Thanks! – noob Nov 03 '16 at 16:28
  • 1
    Awesome, using react and request native, and wondering what in the world to do with a ReadableStream, and this did the trick. ++ – edencorbin Feb 12 '17 at 01:54
  • 1
    Just a headsup, seems like a no-brainer, but make sure the backend you're hitting is actually providing valid JSON! Definitely not speaking from experience. – abelito Apr 18 '19 at 22:24
  • What if the response of the ReadableStream is an image? – Azurespot Nov 09 '19 at 04:25
  • @Azurespot It depends on what you want to do with the Image. You could call one of these methods https://developer.mozilla.org/en-US/docs/Web/API/Body, for example `const imageBlob = await response.blob();` and then `const objectUrl = URL.createObjectURL(imageBlob); htmlImageElement.src = objectUrl;`. The returned URL is released automatically when the document is unloaded. But if your page has dynamic use, you should release it explicitly by calling window.URL.revokeObjectURL(objectUrl). Or use a FileReader. – Stefan Rein Jan 15 '20 at 13:30
  • 1
    thank you so so so much for this resposne! I was bashing my face against everything not understanding all this "readable stream" api documentation and its all about chunks & text files and crap im not using. this worked! I haven't done backend work in over a year so i'm so rusty, this looks like what I used to learn/write and I knew I was forgetting something like this - this was it! – PhilosophOtter Feb 08 '21 at 20:47
  • 1
    Link is broken, try this: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream – Alex W Sep 23 '21 at 15:20
110

Some people may find an async example useful:

var response = await fetch("https://httpbin.org/ip");
var body = await response.json(); // .json() is asynchronous and therefore must be awaited

json() converts the response's body from a ReadableStream to a json object.

The await statements must be wrapped in an async function, however you can run await statements directly in the console of Chrome (as of version 62).

Noel
  • 3,288
  • 1
  • 23
  • 42
48

response.json() returns a Promise. Try ...

res.json().then(body => console.log(body));

where response is the result of the fetch(...)

pinoyyid
  • 21,499
  • 14
  • 64
  • 115
21

Little bit late to the party but had some problems with getting something useful out from a ReadableStream produced from a Odata $batch request using the Sharepoint Framework.

Had similar issues as OP, but the solution in my case was to use a different conversion method than .json(). In my case .text() worked like a charm. Some fiddling was however necessary to get some useful JSON from the textfile.

Bob Kaufman
  • 12,864
  • 16
  • 78
  • 107
Dan Mehlqvist
  • 309
  • 2
  • 5
  • 3
    Thank you! This worked for me. I am sending an Illuminate http response from my Laravel server with a simple `return $data;`. I was finally able to read this response in the browser with `fetch(...).then(response => response.text()).then(data => console.log(data));` – Cameron Hudson Jul 25 '18 at 15:17
  • i have an api which return jwt token and .then(response => response.text()).then(data => console.log(data)); return undefined in that case – Saurabh Jan 17 '23 at 13:16
19

For those who have a ReadableStream and want to get the text out of it, a short hack is to wrap it in a new Response (or Request) and then use the text method:

let text = await new Response(yourReadableStream).text();
joe
  • 3,752
  • 1
  • 32
  • 41
18

Note that you can only read a stream once, so in some cases, you may need to clone the response in order to repeatedly read it:

fetch('example.json')
  .then(res=>res.clone().json())
  .then( json => console.log(json))

fetch('url_that_returns_text')
  .then(res=>res.clone().text())
  .then( text => console.log(text))
Chris Halcrow
  • 28,994
  • 18
  • 176
  • 206
15

If you just want the response as text and don't want to convert it into JSON, use https://developer.mozilla.org/en-US/docs/Web/API/Body/text and then then it to get the actual result of the promise:

fetch('city-market.md')
  .then(function(response) {
    response.text().then((s) => console.log(s));
  });

or

fetch('city-market.md')
  .then(function(response) {
    return response.text();
  })
  .then(function(myText) {
    console.log(myText);
  });
Alexis Tyler
  • 1,394
  • 6
  • 30
  • 48
AlexChaffee
  • 8,092
  • 2
  • 49
  • 55
4

I dislike the chaining thens. The second then does not have access to status. As stated before 'response.json()' returns a promise. Returning the then result of 'response.json()' in a acts similar to a second then. It has the added bonus of being in scope of the response.

return fetch(url, params).then(response => {
    return response.json().then(body => {
        if (response.status === 200) {
            return body
        } else {
            throw body
        }
    })
})
Mardok
  • 1,360
  • 10
  • 14
  • The chaining `then` helps you retrieve the final resolved value (the `body`). Nesting them prevents you from being able to get the `body` value without a callback or some mechanism of the sort. Imagine this: `let body = await fetch(...).then(res => res.json()).then(data => data)`. This wouldn't work in the nested way. To check for `response.status` you can always `throw` an exception inside the first `then`, and add a `catch` to the whole promise chain. – Óscar Gómez Alcañiz May 28 '19 at 11:27
  • 1
    AGREE. Preferred in enterprise environment. : ) – Nash Worth Nov 10 '20 at 20:28
2

Another approach could be to consume/process the incoming stream of data in chunks:

async function toJSON(body) {
  const reader = body.getReader();
  const decoder = new TextDecoder();
  const chunks = [];

  async function read() {
    const { done, value } = await reader.read();

    if (done) {
      return JSON.parse(chunks.join(''));
    }

    const chunk = decoder.decode(value, { stream: true });
    chunks.push(chunk);
    return read();
  }

  return read();
}

const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const jsonData = await toJSON(response.body);

console.log(jsonData);

This approach is especially useful when the incoming data is too large or if you wish to start processing the data as soon as its chunks are available.

Wrote a blog post for those interested in learning more.

designcise
  • 4,204
  • 1
  • 17
  • 13
  • 1
    Other answers lose the interest of using a readable stream by calling methods that wait and return the final result at the end whereas yours allows to use the incoming data. Keep up the good work. Your blog post is helpful to me. – gouessej Jul 25 '23 at 08:19
0

here is how I implemented it. In this case the api is returning a ndjson as a stream, and I am reading it in chunks. In ndjson format, data is split by new lines, so each line by itself is a basic json which I parsed and added to fetchedData variable.

var fetchedData = [];

fetch('LinkGoesHere', {
    method: 'get',
    headers: {
        'Authorization': 'Bearer TokenGoesHere' // this part is irrelevant and you may not need it for your application
    }
})
.then(response => {
    if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.body.getReader();
})
.then(reader => {
    let partialData = '';

    // Read and process the NDJSON response
    return reader.read().then(function processResult(result) {
        if (result.done) {
            return;
        }

        partialData += new TextDecoder().decode(result.value, { stream: true });
        const lines = partialData.split('\n');

        for (let i = 0; i < lines.length - 1; i++) {
            const json = JSON.parse(lines[i]);
            fetchedData.push(json); // Store the parsed JSON object in the array
        }

        partialData = lines[lines.length - 1];

        return reader.read().then(processResult);
    });
})
.then(() => {
    // At this point, fetchedData contains all the parsed JSON objects
    console.log(fetchedData);
})
.catch(error => {
    console.error('Fetch error:', error);
});
Emre Bener
  • 681
  • 3
  • 15
-3

I just had the same problem for over 12 hours before reading next, just in case this helps anyone. When using nextjs inside your _api page you will need to use JSON.stringify(whole-response) and then send it back to your page using res.send(JSON.stringify(whole-response)) and when it's received on the client side you need to translate it back into json format so that it's usable. This can be kinda figured out by reading their serialization section. Hope it helps.

  • Consider formatting your code – Ingenious_Hans Jun 01 '22 at 17:19
  • I had the same problem about response.json() is undefined and it looks like that i did not await the fefch method: Always make sure to await asynchronous actions guys , const response = await fetch(url+path) – Marinos TBH Apr 17 '23 at 09:24