4

I'm trying to wrap my head around the correct way to indicate a failure within a .then().

If the promise doesn't fail, (I.e. the operation that returns the promise does it' job properly, such as an AJAX request that returns a status 200), but I decide that the result isn't valid, usually I'd do a popup, explaining the issue to the user, and do a "return false;" to exit the method early.

However, with promises, if from within the .then(), I want to do something similar, I've been lead to believe that what I should do is throw an error instead, and presumably let this get caught by the .catch() that I've chained on.

My concern is that I want to distinguish between an operation within a promise that succeeded, but which I don't like the result of, and a failed operation within a promise.

For example, if I perform an AJAX call and it fails with a 404, that's not really recoverable, so it seems appropriate to reject with a popup saying something like "something went wrong".

However, if the AJAX request is successful (returns a status 200), but the response indicates that something isn't right (Like the user didn't fill out a field with a correct value), then I'd like to handle that a certain way, which might involve not just a popup with a message (e.g. maybe DOM manipulations, red text etc, things that I might not want to do if it's a 404).

Below are 2 examples to better explain what I mean.

The first being the original implementation with callbacks and the second being with promises (Wrapping the ajax call with the Q promise library to make it a proper promise).

Callback version:

    $.ajax({
    url: "/cars/1",
    type: "GET",
    contentType: "application/json; charset=utf-8",
    dataType: "json"
})
.done(function (data) {
    if (!data.IsSuccessful) {
        //The request was successful (status 200), but the server is returning IsSuccessful=false
        alert(data.Message);//message says something like "we have that car in our catalogue but we don't have it in stock"
        return false;//early exit from .done()
    }

    //if it gets here, everything is good and I can do something with the result
})
.fail(function (data) {
    //The request actually failed due to a generic status 500 error which has something I don't necessarily want to expose in a popup to the user
    alert("Something went wrong");

});

Promise version:

    var myPromise = Q(
    $.ajax({
        url: "/cars/1",
        type: "GET",
        contentType: "application/json; charset=utf-8",
        dataType: "json"
    })
);

myPromise.then(function (data) {
    if (!data.IsSuccessful) {
        throw new Error(data.Message);
    }

    //all good, lets do something with the result
})
.catch(function (error) {

    //what is error?
    //How do I know if it's one that I want to show to the user or not?

}).done();

In the promise version, if the request returns a 404 it will end up in the .catch() immediately right?

If data.IsSuccessful==false, then it will also end up in the .catch()?

What if I want to treat both failures differently, how would I go about that?

I'm not calling resolve or reject anywhere, is that problematic?

I'd like to make sure I'm following best practices as much as possible.

Steviebob
  • 1,705
  • 2
  • 23
  • 36
  • Why not just rely on status codes to do their job? Why would you serve up a 200 response with a `isSuccessful: false` property in it? The 2xx response codes all mean that the request *was successful*. – user229044 May 21 '16 at 15:45
  • Because he might want to display some value that can only be retrieved by the database and thus needs to return this value to the client. This gives a status 200 from AJAX. – PIDZB May 21 '16 at 15:49
  • @Abayob can return data in headers for non `200` – charlietfl May 21 '16 at 15:59
  • @charlietfl How? And can you interpret that with JavaScript? Do you have an example? – PIDZB May 21 '16 at 16:01
  • @Abayob you set header message at server and check response status and if it is a custom status read the header which is part of $.ajax xhr object – charlietfl May 21 '16 at 16:02
  • @charlietfl Oke, nice. I rest my case! – PIDZB May 21 '16 at 16:05
  • @meagar you're right, I knew someone would make that point. My example isn't great. What I'm trying to get across is, if the operation returns a 200, but I want to throw based on something in the result, I'd like to treat that custom logic throw differently to how I would with a 404 for example – Steviebob May 21 '16 at 16:05

1 Answers1

4

TL;DR: You can use rejections for control flow, but in general they are for exceptional cases only


In the promise version, if the request returns a 404 it will end up in the .catch() immediately right?

Yes.

If data.IsSuccessful==false, then it will also end up in the .catch()?

Yes.

I'm not calling resolve or reject anywhere, is that problematic?

Not at all. You're not using the Promise constructor (which you don't need, as you already have a promise for the ajax result).

What if I want to treat both failures differently, how would I go about that?

Throw different kinds of errors so that you can distinguish them. Give them names, add special properties, do subclassing (in ES6 especially), or just look at the message.

with promises, if from within the .then(), I want to do something similar, I've been lead to believe that what I should do is throw an error instead

Not necessarily. You can do exactly the same as you did without promises - put an if-else in the callback (or an if with an early return if you prefer).

In terms of control flow and their result values,

.then(function(res) {
    if (!res.isOK) {
        // do something
        return false;
    }
    // do something else
}).catch(function(err) {
    // handle something
})

and

.then(function(res) {
     if (!res.isOK)
         throw new MyError(res);
     // do something else
}).catch(function(err) {
     if (err instanceof MyError) {
         // do something
         return false;
     }
     // handle something
})

are pretty much equivalent (except for exceptions in do something and with code other than this throwing MyErrors). The major difference is when you want to chain additional .then(…) invocations between the then and catch. If you don't, just choose whatever you like better.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for your answer. Can you please clarify your last paragraph for me? Are you saying that if I do want to chain additional .then()s off of the .then() that I'm throwing an exception from, I need to be mindful that they wont get called as it will jump straight to the .catch()? – Steviebob May 21 '16 at 17:19
  • @Steviebob: Yes, exactly. That might or might not be exactly what you want. If you don't want to get them called and still want to use `if`-`else`, you can nest the `then` callbacks though. – Bergi May 21 '16 at 18:24