38

fetch() returns promise which (if successful) resolves to a Response object. A very common thing to do is immediately call Response.json() to convert the response body to a JSON object.

If the response body isn't valid JSON, then the Response.json() promise fails with an error. The message is something along the lines of:

Unexpected token X in JSON at position 0

That's not very helpful when trying to diagnose the problem; ideally I'd like to be able to see the content from the server (which is often an error message).

However, it appears that you can only read the stream at Response.body once (at least in Chrome). (There's even a read-only Response.bodyUsed flag.) That has already happened when Response.json() tries to convert the body to JSON, so the body appears to be lost forever in the event of a JSON parsing failure.

Is there any way to recover the original response body... short of manually reading it (and then converting to JSON) when the original fetch Promise resolves?

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Craig Walker
  • 49,871
  • 54
  • 152
  • 212
  • 5
    You could call `response.text()` instead to read the returned data even if it's not valid JSON, and you can clone a response, but ideally your server should **always** return JSON when you expect JSON, even the errors should be returned as JSON. – adeneo Nov 08 '16 at 23:04
  • **See Also**: [Failed to execute 'json' on 'Response': body stream is locked](https://stackoverflow.com/q/53511974/1366033) – KyleMit May 05 '21 at 16:49

4 Answers4

47

Use Response.clone() to clone Response

let clone = response.clone();

Alternatively, use Response.body.getReader() which returns a ReadableStream to read Response as a stream, TextDecoder() to convert Uint8Array data stream to text.

guest271314
  • 1
  • 15
  • 104
  • 177
  • @CraigWalker See [How can I append DOM elements as they stream in from the network?](http://stackoverflow.com/questions/38413400/how-can-i-append-dom-elements-as-they-stream-in-from-the-network/) – guest271314 Nov 08 '16 at 23:10
  • 1
    clone() looked like the answer, but it fails if you've already called .json(): "TypeError: Failed to execute 'clone' on 'Response': Response body is already used" So resp.text() followed by manual json parsing is one option, but +1 for Jaromanda's suggestion to clone() before calling json()! – xander Feb 07 '17 at 22:45
  • 1
    @xander Yes, `.clone()` needs to be called before `body` is read. This is why `javascript` at Answer is `response.clone()`. Have you read the specification https://fetch.spec.whatwg.org/#body-mixin, https://fetch.spec.whatwg.org/#bodies, https://fetch.spec.whatwg.org/#dom-response-clone ? – guest271314 Feb 08 '17 at 16:50
  • 3
    I'm getting `Failed to execute 'clone' on 'Response': Response body is already used` – Pacerier Oct 16 '17 at 04:00
  • @Pacerier Then `Response.body` has already been used at the code. Can you create a jsfiddle https://jsfiddle.net or plnkr https://plnkr.co to demonstrate? – guest271314 Oct 16 '17 at 04:01
  • 1
    thanks @guest271314 for the answer , here is a working example if any one needed more help https://codesandbox.io/s/parcel-sandbox-5sy2h – Mohamed Nov 06 '19 at 10:13
12

I had to deal with an API that occasionally botched the JSON response - before returning response.json() I made a clone of the response object. using a catch block, I can determine if the error is a SyntaxError, and proceed to fix the error using the text result of the response clone

a little like this:

var brokenJson = function (url) {
    var responseCopy;
    return fetch(url)
    .then(function (response) {
        responseCopy = response.clone();
        return response.json();
    }).catch(function (err) {
        if (err instanceof SyntaxError) {
            return responseCopy.text()
            .then(function(data) {
                return fixJson(data);
            });
        }
        else {
            throw err;
        }
    }).then(function (json) {
        // do things
    });
};

fixJson is just a function that fixes the received data - in my case, when it was broken JSON, it was always broken the same way - I think it had an extra leading { or trailing } - can't recall

re-reading the question, you're more likely to want to log the error to the console rather than fix the json - easy rewrite:

var brokenJson = function (url) {
    var responseCopy;
    return fetch(url)
    .then(function (response) {
        responseCopy = response.clone();
        return response.json();
    }).catch(function (err) {
        if (err instanceof SyntaxError) {
            return responseCopy.text()
            .then(function(text) {
                console.error(text);
                throw err;
            });
        }
        else {
            throw err;
        }
    }).then(function (json) {
        // do things
    });
};
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
0

Assigning the response.json() to a variable and returning it worked for me. clone() was again saying its locked.

fetch("http://localhost:3000/watchlist")
    .then(response => {
      var res = response.json();
      return res;
    })
    .then(data => {
      console.log(data);
      this.setState({ data });
    });
0

I used JSON.parse(response.resp.data) as somehow clone didn't work.

Pencilcheck
  • 2,664
  • 3
  • 25
  • 14