Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cleanest way to handle custom errors with fetch & ES6 promise

I am trying to intelligently handle the success/error responses from our API using fetch & ES6 promises.

Here is how I need to handle response statuses:

204: has no json response, but need to treat as success
406: should redirect to sign in
422: has json for error message
< 400 (but not 204): success, will have json
>= 400 (but not 422): error, will not have json

So, I am struggling with how to write this cleanly.

I have some less than stellar code working right now that looks like this:

fetch()
  .then(response => checkStatus(response))
  .then(parseJSON)                           //will throw for the 204
  .then(data => notify('success', someMsg))
  .catch(error => checkErrorStatus(error))
  .then(parseJSON)
  .then(data => notify('error', dataForMsg)
  .catch(error => notify('error', someGenericErrorMsg)

But it seems pretty weird to use catch twice and I don't know how to deal with that 204 just yet.

Also, just to clarify checkStatus and checkErrorStatus do a similar thing:

export function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response
  } else {
    let error = new Error(response.statusText)
    error.response = response
    throw error
  }
}

function checkErrorStatus(error) {
  if(error.response.status === 422) {
    return error.response
  } else {
    let error = new Error(response.statusText)
    error.response = response
    throw error
  }
}

Any suggestions for cleaning this up?

like image 610
jasongonzales Avatar asked Nov 25 '15 02:11

jasongonzales


Video Answer


2 Answers

I think you can write it out pretty easily:

fetch(…).then(response => {
    if (response.ok)
        return response[response.status == 204 ? "text" : "json"]();
    if (response.status == 422)
        return response.json().then(err => { throw err; });
    if (response.status == 406)
        var error = new AuthentificationError(response.statusText); // or whatever
    else
        var error = new Error(response.statusText)
    error.response = response
    throw error;
})
like image 109
Bergi Avatar answered Oct 09 '22 20:10

Bergi


Following on from Bergi's solution, you might consider mechanising the handling of responses with a fairly simple ResponseHandler() object;

function ResponseHandler() {
    this.handlers = [];
    this.handlers[0] = function() {}; // a "do nothing" default handler
}
ResponseHandler.prototype.add = function(code, handler) {
    this.handlers[code] = handler;
};
ResponseHandler.prototype.handle = function(response) {
    var h = this.handlers,
        s = response.status,
        series = Math.floor(s / 100) * 100; // 100, 200, 300 etc
    (h[s] || h[series] || h[0])(response); // sniff down the line for a specific/series/default handler, then execute it.
};

In use :

// create an instance of ResponseHandler() and add some handlers :
var responseHandler = new ResponseHandler();
responseHandler.add(204, function(response) {...}); // specific handler
responseHandler.add(422, function(response) {...}); // specific handler
responseHandler.add(406, function(response) {...}); // specific handler
responseHandler.add(200, function(response) {...}); // 200 series default handler
responseHandler.add(400, function(response) {...}); // 400 series default handler
responseHandler.add(0, function(response) {...}); // your overall default handler

// then :
fetch(…).then(response => { responseHandler.handle(response); });

You would lose the efficiencies of a hard coded solution such as Bergi's, but potentially benefit from improved manageability and reusability.

like image 40
Roamer-1888 Avatar answered Oct 09 '22 19:10

Roamer-1888