admin管理员组

文章数量:1296505

I am starting with E6 Promises. I like them very much, but there is a crucial concept around error handling that I don't understand and would love some clarification on.

Let's assume the following simple function that returns a promise:

    function promiseString(str, timeout, doResolve) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (doResolve) {
                    resolve(str);
                } else {
                    reject(new Error("Rejecting " + str));
                }
            }, timeout);
        });
    }

It is pretty straightforward, just returns a promise for the string that was passed to it, and causes that promise to be resolved or rejected (based on the third argument) in "timeout" milliseconds.

I can consume this pletely as expected as follows:

            promiseString("One", 100, true)
                .then((str) => { console.log("First then is " + str); return promiseString(str + " two", 100, true); })
                .then((str) => { console.log("Second then is " + str); return promiseString(str + " three", 100, true); })
                .then((str) => console.log(str))
                .catch((err) => console.error(err));

If alter the third argument to from "true" to "false" in any of the calls in this chain, my error is caught as expected and send to console.error().

However, now imagine the following (similarly silly) function for constructing a promising object:

    function DoublePromiser(str1, str2, doResolve) {
        this.promise = new Promise((resolve, reject) => {
            promiseString(str1, 100, doResolve)
                .then((s1) => promiseString(s1 + str2, 100, doResolve))
                .then((s2) => resolve(s2));
        });
    }

Imagine now that I consume this code as follows, with everything resolving and nothing rejecting, (doResolve is set to true):

            var dp = new DoublePromiser("Big", "Promise", true);
            dp.promise
                .then((s) => console.log("DoublePromise: " + s))
                .catch((err)=>console.log("I did catch: ", err.message));

As would be expected, I see the following in the console:

DoublePromise: BigPromise

However, now I alter the consuming code, setting doResolve to "false" (which causes my promise routine to reject):

            var dp = new DoublePromiser("Big", "Promise", false);
            dp.promise
                .then((s) => console.log("DoublePromise: " + s))
                .catch((err)=>console.log("I did catch: ", err.message));

Because of my understanding of how errors should "bubble up", I would expect the console to log as follows:

I did catch: Rejecting Big

But it does not. Instead, the console shows an uncaught error:

Uncaught (in promise) Error: Rejecting Big

I only get what I expect (and desire) if I add a catch to the end of the chain in the DoublePromiser, like this:

    function DoublePromiser(str1, str2, doResolve) {
        this.promise = new Promise((resolve, reject) => {
            promiseString(str1, 100, doResolve)
                .then((s1) => promiseString(s1 + str2, 100, doResolve))
                .then((s2) => resolve(s2))
                .catch((err) => reject(err)); // ADDING THIS TO MAKE IT WORK
        });
    }

Now I get what I expect, the error is not uncaught. But this seems counter to the whole idea that errors bubble up, and it seems weird to catch an error just to re-reject the same error.

Am I missing a way of getting this to work simply?

Am I missing some fundamental concept?

I am starting with E6 Promises. I like them very much, but there is a crucial concept around error handling that I don't understand and would love some clarification on.

Let's assume the following simple function that returns a promise:

    function promiseString(str, timeout, doResolve) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (doResolve) {
                    resolve(str);
                } else {
                    reject(new Error("Rejecting " + str));
                }
            }, timeout);
        });
    }

It is pretty straightforward, just returns a promise for the string that was passed to it, and causes that promise to be resolved or rejected (based on the third argument) in "timeout" milliseconds.

I can consume this pletely as expected as follows:

            promiseString("One", 100, true)
                .then((str) => { console.log("First then is " + str); return promiseString(str + " two", 100, true); })
                .then((str) => { console.log("Second then is " + str); return promiseString(str + " three", 100, true); })
                .then((str) => console.log(str))
                .catch((err) => console.error(err));

If alter the third argument to from "true" to "false" in any of the calls in this chain, my error is caught as expected and send to console.error().

However, now imagine the following (similarly silly) function for constructing a promising object:

    function DoublePromiser(str1, str2, doResolve) {
        this.promise = new Promise((resolve, reject) => {
            promiseString(str1, 100, doResolve)
                .then((s1) => promiseString(s1 + str2, 100, doResolve))
                .then((s2) => resolve(s2));
        });
    }

Imagine now that I consume this code as follows, with everything resolving and nothing rejecting, (doResolve is set to true):

            var dp = new DoublePromiser("Big", "Promise", true);
            dp.promise
                .then((s) => console.log("DoublePromise: " + s))
                .catch((err)=>console.log("I did catch: ", err.message));

As would be expected, I see the following in the console:

DoublePromise: BigPromise

However, now I alter the consuming code, setting doResolve to "false" (which causes my promise routine to reject):

            var dp = new DoublePromiser("Big", "Promise", false);
            dp.promise
                .then((s) => console.log("DoublePromise: " + s))
                .catch((err)=>console.log("I did catch: ", err.message));

Because of my understanding of how errors should "bubble up", I would expect the console to log as follows:

I did catch: Rejecting Big

But it does not. Instead, the console shows an uncaught error:

Uncaught (in promise) Error: Rejecting Big

I only get what I expect (and desire) if I add a catch to the end of the chain in the DoublePromiser, like this:

    function DoublePromiser(str1, str2, doResolve) {
        this.promise = new Promise((resolve, reject) => {
            promiseString(str1, 100, doResolve)
                .then((s1) => promiseString(s1 + str2, 100, doResolve))
                .then((s2) => resolve(s2))
                .catch((err) => reject(err)); // ADDING THIS TO MAKE IT WORK
        });
    }

Now I get what I expect, the error is not uncaught. But this seems counter to the whole idea that errors bubble up, and it seems weird to catch an error just to re-reject the same error.

Am I missing a way of getting this to work simply?

Am I missing some fundamental concept?

Share edited Jul 28, 2016 at 0:44 jfriend00 708k103 gold badges1k silver badges1k bronze badges asked Jul 28, 2016 at 0:19 Stephan GStephan G 3,4876 gold badges35 silver badges56 bronze badges 5
  • 1 These 100% theoretical questions using made up code that don't state a real problem just don't work very well on stack overflow. – jfriend00 Commented Jul 28, 2016 at 0:37
  • Also, you are using the promise constructor anti-pattern: github./petkaantonov/bluebird/wiki/Promise-anti-patterns – jfriend00 Commented Jul 28, 2016 at 0:38
  • @estus - I edited their title. – jfriend00 Commented Jul 28, 2016 at 0:50
  • @jfriend00 Thanks. Nice answer btw, it nails this down. – Estus Flask Commented Jul 28, 2016 at 0:53
  • Reopening because, though that other answer contains info that can be used to answer this question, they are not even close to the same actual question. Nobody who read that other title or question would think that is the same question as this question. – jfriend00 Commented Jul 28, 2016 at 2:35
Add a ment  | 

1 Answer 1

Reset to default 10

You are using a promise constructor anti-pattern. Don't wrap an existing promise in another promise you make yourself as that just makes you do lots of extra work to make things work properly and since most people don't do that extra work properly, it's also very prone to programming mistakes. Just return the promise you already have.

Change this:

function DoublePromiser(str1, str2, doResolve) {
    this.promise = new Promise((resolve, reject) => {
        promiseString(str1, 100, doResolve)
            .then((s1) => promiseString(s1 + str2, 100, doResolve))
            .then((s2) => resolve(s2))
            .catch((err) => reject(err)); // ADDING THIS TO MAKE IT WORK
    });
}

to this:

function DoublePromiser(str1, str2, doResolve) {
    return promiseString(str1, 100, doResolve)
       .then((s1) => promiseString(s1 + str2, 100, doResolve));
}

And, then just use it as a function:

DoublePromiser("Big", "Promise", false).then(...);

Recap: You nearly always want to return inner promises from within .then() handlers because this allows nested errors to propagate upwards and also properly chains/sequences async operations.

And, you want to avoid wrapping new promises around existing promises nearly always because you can just chain to and/or return the existing promise you already have.

Also, be aware that if you do a .catch(), that will "handle" a rejected promise and return a new non-rejected promise, continuing the promise chain from there unless inside the .catch() handler you return a rejected promise or throw an exception. So, this:

p.catch((err) => console.log(err)).then(() => console.log("chain continues"))

will happily do both console.log() statements because the .catch() "handled" the promise so the promise chain happily continues.


As I said in my earlier ments, these 100% theoretical discussions are hard to get to exactly what you really want to acplish (we have to guess what the real problem is) without a 20 page tutorial on how promises work that covers a lot of stuff. It's a lot better if you post a real-world problem you're trying to solve with this technique and we can show/explain the best way to do that in a few lines of code and a few paragraphs of explanation.

本文标签: javascriptES6 Promise Errors not bubbling up as expectedStack Overflow