Search code examples
node.jsjasminebluebirdbabeljs

Unhandled rejection error with Bluebird, Jasmine and an async function


Consider the following (contrived) example:

import Promise from 'bluebird';

const resolvedPromise = () => {
    return Promise.resolve('Test');
};

const asyncFunc = async (asyncResource) => {
    await resolvedPromise();
    await asyncResource.fetchItem();
};


describe('async/await', () => {
    it('should throw an error', (done) => {
        const mockAsyncResource = jasmine.createSpyObj('mockAsyncResource', ['fetchItem']);

        const mockError = Promise.reject(new Error('HTTP 401 - Access Denied'));

        mockAsyncResource.fetchItem.and.returnValue(mockError);

        return asyncFunc(mockAsyncResource).catch(err => {
            expect(err.message).toEqual('HTTP 401 - Access Denied');
            done();
        });
    });
});

I receive the following output:

1) async/await
Unhandled rejection Error: HTTP 401 - Access Denied 
... (error stack trace)

✔ should throw an error

The spec passes, but Bluebird complains about the unhandled rejection error.

Because of the blocking nature of awaits, does the mockError report it's unhandled rejection before the second await has a chance to call (and handle) the mockError promise?

What is the best way to handle this scenario? I've discovered promise.suppressUnhandledRejections(), is it as simple as using this?


Solution

  • I think this is a problem caused by mixing 2 different types of promises. You are creating your resolved/rejected mock promises using bluebird's Promise but the await returns the native Promise.

    If you try the following on the chrome console, you will see it works as you expected when only using native promises:

    let resolvedPromise = () => {
        return Promise.resolve('Test');
    };
    
    let asyncFunc = async (asyncResource) => {
        await resolvedPromise();
        await asyncResource.fetchItem();
    };
    
    let mockAsyncResource = {fetchItem: () => Promise.reject(new Error("foo"))};
    
    asyncFunc(mockAsyncResource).catch((e) => console.log('handled'))
    

    If you want to stick with creating bluebird promises, you might need to wrap your async function with bluebird's Promise.try, although I haven't been able to test this:

    let asyncFunc = async (asyncResource) => {
        return Promise.try(async () => {
            await resolvedPromise();
            await asyncResource.fetchItem();
        });
    };