Search code examples
promisees6-promise

ES6 Promise - Why does throw from catch() and throw from then() not behave the same way?


I have this piece of ES6 code that invokes a java backend. The java backend normally returns status code 200 and a json payload, but sometimes status code 500 and a json payload. For 200 I want to deserialize the json and pass the resulting object up the promise chain. For 500 I want to deserialize the json and throw the resulting object up the promise chain, i.e. have it hit the catch blocks.

The following code does almost what I want:

invoke(className, methodName, args) {
    return this.httpClient
        .fetch('/api/' + className + "/" + methodName,
            {
                method: 'POST',
                body: json(args)
            })
        .catch(response => {
            // Function A
            throw response.json();
        })
        .then(response => {
            // Function B
            return response.json();
        });
}

this.invoke("TestService", "testMethod", {a: 1, b: 2})
    .then(response => {
        // Function C
        console.log(response); // prints the actual json object which I expect
    }).catch(response => {
        // Function D
        console.log(response); // prints: [object Promise]
    });
  • Function A gets invoked for 500. Good.
  • Function B gets invoked for 200. Good.
  • response.json() returns a promise of a deserialized object in both A and B. Good.
  • Function A causes function D to be invoked. Good.
  • Function B causes function C to be invoked. Good.
  • The argument, response, to function C is the deserialized object. Good.
  • But, the argument, response, in function D is not the deserialized object, but a promise of a deserialized object.

I've been working at this for a while now, but I'm having trouble explaining to google what my problem is.

Question: Why does returning a promise 'unwrap' the promise for the next function, but throwing a promise passes the promise itself into the next function?

Is there any way I can achieve what I want, which is for function D to get the 'unwrapped' object just like function C?


Solution

  • The reason you get the promise in catch callback, is that this is by specification.

    For returned values within a then or catch callback, the rule is that that promise must resolve before the outer promise resolves, and that the resolved value should be the value promised by the returned promise. From the Promises/A+ specs 2.2.7.1:

    If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

    Chapter 3 in that specification further explains what that means.

    However, this is not true for thrown exceptions, as can be seen in point 2.2.7.2:

    If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.

    No attempt is made to recognise e as a promise nor to wait for its resolution. It is thrown as is, and that will be what you get in the next catch.

    Solution

    The solution is thus to return a promise, but a promise that will throw the resolved value (not a promise):

    return response.json().then(data => { throw data });