Bluebird: Promises with both .then and passed to .all always throw Unhandled rejection Error

Created on 11 Feb 2015  ยท  10Comments  ยท  Source: petkaantonov/bluebird

I've had this issue for a while and just spent the past day and a half trying to figure out the source of the problem. I've finally narrowed it down.

The following code results in an Unhandled rejection Error:

var promise1 = function () {
  return Promise.reject();
};

promise1()
.then(function () {
  console.log('great success');
});

Promise.all([promise1])
.catch(function (err) {
  console.log('handled the error');
});

Is this intended behavior?

Most helpful comment

To add on to this explanation a bit (Petka, correct any of this if it's wrong):

var promise1 = function () {
  return Promise.reject();
};

promise1()
.then(function () {
  console.log('great success');
});

Promise.all([promise1])
.catch(function (err) {
  console.log('handled the error');
});

Here's what's actually happening here:

  1. You define promise1, and have it return a rejected promise when called.
  2. In the second statement, you call promise1(), which returns the rejected promise immediately (although the handling is asynchronous).
  3. Since the handling is async, you call Promise.all with an array of functions. Because the function isn't being called (i.e. [promise1()], no promise is returned, and per Promises/A+, this is interpreted as a value. Even if you comment out the second block, you won't reach the catch function.
  4. The .then function sees a rejection and propagates it until it crashes the process since there's no handler available.

Here's code that does what you're looking for:

var promise1 = function () {
  return Promise.reject('uh oh'); // or, throw new Error('uh oh') - gives a callstack
};

Promise.all([promise1().then(function() {
  console.log('great success');
})]).catch(function (err) {
  console.log('handled the error');
});

This puts the .then() within the chain containing .catch().
The structure of this is a bit strange, but it's a simple example - normally, the .then logic would be inside promise1().

All 10 comments

Yes it is intended behavior, not handling errors is a bug in the application and that's what you are being made aware of. To handle a rejected promise, you can use a catch handler:

promise1()
.then(function () {
  console.log('great success');
})
.catch(function(e) {
  console.log("handled the error");
});
promise1()
.then(function () {
  console.log('great success');
})
.catch(function(e) {
  console.log("handled the error");
});

promise1()
.then(function () {
  console.log('great success2');
})
.catch(function(e) {
  console.log("handled the error");
});

promise1()
.catch(function (err) {
  console.log('handled the error');
});

Also don't reject with no argument (as that's the same as rejecting with undefined) this is equal to saying throw null in Java or something.

To add on to this explanation a bit (Petka, correct any of this if it's wrong):

var promise1 = function () {
  return Promise.reject();
};

promise1()
.then(function () {
  console.log('great success');
});

Promise.all([promise1])
.catch(function (err) {
  console.log('handled the error');
});

Here's what's actually happening here:

  1. You define promise1, and have it return a rejected promise when called.
  2. In the second statement, you call promise1(), which returns the rejected promise immediately (although the handling is asynchronous).
  3. Since the handling is async, you call Promise.all with an array of functions. Because the function isn't being called (i.e. [promise1()], no promise is returned, and per Promises/A+, this is interpreted as a value. Even if you comment out the second block, you won't reach the catch function.
  4. The .then function sees a rejection and propagates it until it crashes the process since there's no handler available.

Here's code that does what you're looking for:

var promise1 = function () {
  return Promise.reject('uh oh'); // or, throw new Error('uh oh') - gives a callstack
};

Promise.all([promise1().then(function() {
  console.log('great success');
})]).catch(function (err) {
  console.log('handled the error');
});

This puts the .then() within the chain containing .catch().
The structure of this is a bit strange, but it's a simple example - normally, the .then logic would be inside promise1().

This cleared a lot up for me, thanks.

Hmm so that is why promise.should.be.rejected still throws? (using shouldjs/should-promise).

Any way to fix that? It's nicer to write the above instead of promise.then(throwerFn, acceptorFn)

I seem to be hitting this, too. It would seem that Promise.all is "handling" the rejection, but clearly it's not. I guess that could be documented?

I am seeing this error with the following structure:
js new Promise((accept, reject) =>reject("test")) .then( () => console.log("ok"), () => console.log('err') ) .catch( (...args) => console.log("wtf", args) )

And I am not seeing this behavior on native Chrome or Firefox promises. What can be going on in here?

same issue as @cfv1984

I'm using async/await and definitely catching my exceptions, but I still get this error.

Sorry to wake up an old thread, I've this weird issue with jasmine testing framework and bluebird: it sometimes (not consistently) produce errors in the console log like this one:

Unhandled rejection Error: dummy
    at UserContext.<anonymous> (/home/mastro/ws/my-project-path/spec/unit/logic.spec.js:246:89)
    at UserContext.arguments.(anonymous function) (/home/mastro/ws/my-project-path/node_modules/jasmine-promises/dist/jasmine-promises.js:35:30)
    at attempt (/home/mastro/ws/platform/nsp-gamification-loyalty-services/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:4297:26)
    at QueueRunner.run (/home/mastro/ws/platform/nsp-gamification-loyalty-services/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:4217:20)
    at Timeout.runNext [as _onTimeout] (/home/mastro/ws/platform/nsp-gamification-loyalty-services/node_modules/jasmine-core/lib/jasmine-core/jasmine.js:4257:20)
    at tryOnTimeout (timers.js:228:11)
    at Timer.listOnTimeout (timers.js:202:5)

Those are actually printed in the logs but do not make the test fail. In fact the test succeed, a test exhibiting the issue may look something like this:

        it('should throw if the check of `type` and `config` throws', () => {
            spyOn(actionHandler, 'validateActionConfig').and.returnValue(Promise.reject(new Error('dummy'))); 
            return sut.createAction(principal, action)
                .then(() => {
                    fail('Promise rejection expected, instead fulfilled');
                })
                .catch((err) => {
                    expect(actionHandler.validateActionConfig).toHaveBeenCalled();
                    expect(err.message).toBe('dummy');
                });
        });

The spyOn is jasmine way of testing mocked dependencies. sut is the subject under test. This test check our function createAction forward any rejectiong error from the call to actionHandler.validateActionConfig() by returning a rejection promise when the function is called.
That promise is then catched, as you can see, and we also check the error is exactly the one throw there. So the promise IS catched, but somehow it is detected as uncatched.

Furthermore this is not an on/off problem. Some test randomly exhibit this behavior, some never do.

I'm not sure if the issue here is on the jasmine or bluebird side. (I also posted this here https://github.com/jasmine/jasmine-npm/issues/124)
Can you shed some light on what's possibly going on?
Thanks!

related dependencies

โ”œโ”€โ”€ [email protected]
โ”‚
โ”œโ”€โ”ฌ [email protected]
โ”‚ โ””โ”€โ”€ [email protected]
โ”œโ”€โ”€ [email protected]
โ”œโ”€โ”ฌ [email protected]
โ”‚ โ””โ”€โ”€ [email protected]
โ”œโ”€โ”€ [email protected]
โ”œโ”€โ”ฌ [email protected]
โ”‚ โ””โ”€โ”€ [email protected]
Was this page helpful?
0 / 5 - 0 ratings

Related issues

capaj picture capaj  ยท  5Comments

SimonSchick picture SimonSchick  ยท  6Comments

julien-f picture julien-f  ยท  5Comments

popod picture popod  ยท  4Comments

divramod picture divramod  ยท  5Comments