1

When a client-side fetch request results in an error on the server side, I'd like to return an error code (400) and a custom message. I don't know how to retrieve both on the client-side elegantly using fetch and promises.

return fetch('/api/something')
    .then(response => response.json())
    .then(json => {
         console.log(json.message)
        // I only have access to the json here.
        // I'd also like to get the response status code 
        // (from the response object) and potentially 
        // throw an error complete with the custom message.
    })
    .catch(function(ex) {
        console.log('Unhandled Error! ', ex);
    });

Thanks!

Rick Jolly
  • 2,949
  • 2
  • 23
  • 31

2 Answers2

5

You only have access to the JSON string since that is what you returned from the onFulfill callback in your first .then(). A better approach would be to return a Promise.all() wrapper that resolves to an array with the original response object as well as the "resolved" JSON object:

return fetch('/api/something')
    .then(response => Promise.all([response, response.json()]))
    .then(([response, json]) => {
        if (response.status < 200 || response.status >= 300) {
            var error = new Error(json.message);
            error.response = response;
            throw error;
        }
        // Either continue "successful" processing:
        console.log('success!', json.message);
        // or return the message to seperate the processing for a followup .then()
        // return json.message;
    })
    .catch(function(ex) {
        console.log('Unhandled Error! ', ex);
    });
Amit
  • 45,440
  • 9
  • 78
  • 110
  • Avoid the [promise constructor antipattern](http://stackoverflow.com/q/23803743/1048572) – Bergi Sep 11 '15 at 22:18
  • @Bergi - yep... Keep falling with that one :-) Much better now, thanks. – Amit Sep 12 '15 at 06:40
  • Ah, I thought of `response => response.json().then(json => ({response, json}))`, but `Promise.all` is probably easier. – Bergi Sep 12 '15 at 10:59
1

Answering my own question.

Edit...

With help from Amit and Felix I've settled on the code below as it's easiest for me to read.

async function format(response) {
    const json = await response.json();
    return {response, json};
}

function checkStatus(response, json) {
    if (response.status < 200 || response.status >= 300) {
        var error = new Error(json.message);
        error.response = response;
        throw error;
    }
    return {response, json};
}

return fetch('/api/something')
    .then((response) => format(response))
    .then(({response, json}) => checkStatus(response, json))
    .then(({response, json}) => {
        console.log('Success!', json.message);
    })
    .catch((error) => {
        if (error && error.response) {
            console.log('error message', error.message);
        } else {
            console.log('Unhandled error!');
        }
    });

...End Edit

Promise.all would have worked for me as described here: How do I access previous promise results in a .then() chain?. However, I find that unreadable. So ES7 async functions to the rescue!

async function formatResponse(response) {
    var json = await response.json();
    response.json = json;
    return response;
}

function checkResponseStatus(response) {
    if (response.status >= 200 && response.status < 300) {
        return response;
    } else {
        var error = new Error(response.json.message);
        error.response = response;
        throw error;
    }
}

function handleResponse(response) {
    console.log('success!', response.json);
}

function handleError(error) {
    if (error && error.response) {
        console.log('error message', error.message);
        console.log('error response code', error.response.status)
    } else {
        console.log('Unhandled error!');
    }
}

return fetch('/api/something')
   .then(formatResponse)
   .then(checkResponseStatus)
   .then(handleResponse)
   .catch(handleError);
Community
  • 1
  • 1
Rick Jolly
  • 2,949
  • 2
  • 23
  • 31
  • 1
    It's seems weird to override `response.json`. Why don't you just call `await response.json` where you need it? I.e. in `checkResponseStatus` and in `handleResponse`. – Felix Kling Sep 11 '15 at 02:01
  • It is weird, but I don't overwrite the `response.json()` promise. I create a new property called `json`. Might not be any better. I could return an object from `formatResponse` (e.g `{response, json}`) instead. I honestly don't know if/how to use `await response.json()` within `checkResponseStatus` and `handleResponse` though. – Rick Jolly Sep 11 '15 at 06:19
  • Oops. Sorry, I do overwrite response.json. – Rick Jolly Sep 11 '15 at 06:28
  • 1
    *"I honestly don't know if/how to use `await response.json()` within `checkResponseStatus` and `handleResponse` though."* exactly like you use it in `formatResponse`. Of course you have to make the other functions async as well. – Felix Kling Sep 11 '15 at 13:25