4

I am using fetch api for fetching an URL that might return:

Response : status = 200, json body = {'user': 'abc', 'id': 1}

or

Response : status = 400 , json body = {'reason': 'some reason'}

or

Response : status = 400 , json body = {'reason': 'some other reason'}

I want to make a separate function request() that I use from various parts of my code as follows:

    request('http://api.example.com/').then(
        // status 200 comes here
        data => // do something with data.id, data.user
    ).catch(
        // status 400, 500 comes here
        error =>  // here error.reason will give me further info, i also want to know whether status was 400 or 500 etc
    )

I am unable to do the split between 200 and 400,500 (i have tried by throwing an error). When I throw an error, I am finding it hard to still extract the JSON body (to use for error.reason).

My current code is as follows:

import 'whatwg-fetch';

/**
 * Requests a URL, returning a promise
 */
export default function request(url, options={}) {

    console.log('sending api request, url = ' + url)

    return fetch(url, options)
        .then(checkStatus)
        .then(parseJSON)
        .then((data) => ({data}))
        .catch((err) => ({err}));
}


function checkStatus(response) {
    if (response.status >= 200 && response.status < 300) {
        return response;
    }

    const error = new Error(response.statusText);
    error.response = response;
    throw error;
}



function parseJSON(response) {
    return response.json();   // json() is a promise itself
}

I have tried to solve this by doing as follows, by inverting the order of .then() calls, but does not work

export default function request(url, options) {
    return fetch(url, options)
        .then(parseJSON)        // note that now first calling parseJSON to get not just JSON but also status. 
        .then(checkStatus)      // i.e. Inverted order of the two functions from before

        .then((data) => ({data}))
        .catch((err) => ({err}));
}

function checkStatus({data, status}) {

    if (status >= 200 && status < 300) {
        return data;
    }
    else {
        // const error = new Error(response.statusText);
        const error = new Error("Something went wrong");
        // error.response = response;
        error.data = data;

        throw error;
    }

}

function parseJSON(response) {
    let jsonBody

    response.json().then(json => {
        jsonBody = json                 // this does not help, i thought it will make jsonBody fill up, but seems its in a diff thread
    })              

    return {
        data: jsonBody,
        status: response.status     // my aim is to send a whole dict with status and data to send it to checkStatus, but this does not work
    }
}
dowjones123
  • 3,695
  • 5
  • 40
  • 83
  • The error comes is in the second callback in .then -> `request('http://api.example.com/').then( function(data){ //success call back, i.e 200 }, function( jqXHR, textStatus, errorThrown) { // error call back. Check the status code here } );` – Developer Sep 06 '16 at 02:05
  • `this does not help` - the issue with that code is that Promises don't make asynchronous code synchronous (the code assumes they do) – Jaromanda X Sep 06 '16 at 02:16
  • duplicate of [Cleanest way to handle custom errors with fetch & ES6 promise](http://stackoverflow.com/q/33907465/1048572) or [fetch: Reject promise with JSON error object](http://stackoverflow.com/q/29473426/1048572)? – Bergi Sep 06 '16 at 06:05

2 Answers2

8

response.json() returns an asynchronous result. You are not returning the object at parseJSON from within .then() chained to response.json(). To correct that issue you can return response.json() promise at parseJSON call and return object containing data and status from within .then() chained to response.json()

function parseJSON(response) {
    return response.json().then(json => {
          return {
                   data: json,
                   status: response.status  
                 }
    })         
}  
guest271314
  • 1
  • 15
  • 104
  • 177
  • 1
    you can actually shorten that functions body to `return response.json().then(json => ({ data: json, status: response.status }));` – Jaromanda X Sep 06 '16 at 02:12
  • @JaromandaX Yes, did not want to further complicate matters at Answer – guest271314 Sep 06 '16 at 02:13
  • looks like the OP is familiar enough with arrow functions to even do `var parseJSON = response => response.json().then(json => ({ data: json, status: response.status }));` – Jaromanda X Sep 06 '16 at 02:13
0

Here's slightly different approach: With a one-liner I create a response-like promise with ok, status and json-as-object (not a promise), then I decide what to do with this object. Generally I reject with response if response.ok is false, otherwise I resolve with only the json-data. Network errors/json-parse-errors are rejected as usual.

fetch(url, options)
    .then(r => r.json().then(json => ({ok: r.ok, status: r.status, json})))
    .then( r => r.ok ? r.json: Promise.reject(r))
dpnmn
  • 493
  • 2
  • 7
  • As far as I know using the spread operator with response doesn't work but results in undefined. So your es6 example is wrong. Can you confirm that? – webwelten Aug 28 '17 at 14:32
  • @webwelten: You're right, and I don't know why I wrote that. I believe it worked for me when I wrote it (I usually test code before I post). However that code (now abondoned) went through a compile step (babel and/or typescript, if I remember correctly) to old-style JS, so perhaps that's the reason. I will remove that line from the answer. – dpnmn Aug 29 '17 at 11:04