Search code examples
node.jspromiseasync-awaitparse-server

Change type of promise returned by async functions to a different type


Edit - While the answers contain a couple of viable solutions, I'll point out that Parse JS SDK 2.0 (and thus Parse-Server 3.0) have been announced, and actually remove Parse.Promise. So, the best solution would be to swap out Parse Promise implementations and use native Promises instead.

I use Parse-Server ( latest version, 2.8.2 ) as a backend. I got very used to promises, and they are heavily ingrained in my code.

I've been starting to use the async / await pattern instead, though. But it isn't playing nicely with all of my existing implementations.

This example will throw an error:

const promiseErrorTestHelper = async () => {
    return Parse.Promise.as();
}

Parse.Cloud.define('promiseErrorTest', async(req, res) => {
    promiseErrorTestHelper().always(
        res.success
    );
});

This one works fine:

const promiseErrorTestHelper = async () => {
    return Parse.Promise.as();
}

Parse.Cloud.define('promiseErrorTest', async(req, res) => {
    promiseErrorTestHelper().then(
        res.success
    );
});

In Parse-Server, .always() is used to pass a callback function whether the promise was rejected or resolved. I have used this throughout my code, and discovered the issue while refactoring some functions to use async / await while adding new functionality.

I understand the issue to be that async functions wrap their result in a promise. So, it's taking my Parse.Promise and converting it to a different type that does not have the .always() method.

Is there by chance a simple way for me to override the functionality of async to return a Parse.Promise instead? Or will I have to rework the always() calls if I want to use async / await?


Solution

  • It appears that you cannot change what type of promise is returned from an async function. It is built-in to return a native promise and is not configurable. There's a similar discussion here about how to make an async function return a Bluebird promise here. The conclusion was that you can't so if you want a Bluebird promise, you have to wrap the async function.


    To continue after an error, the usual way is to use a .catch() that logs the error and doesn't rethrow, thus "handling" the error:

    someFunc().catch(err => {
        // handle the error, continue processing, changes promise chain to resolved
        console.log(err);
        return null;
    }).then(() => {
        // will always get called when original promise resolves or rejects
        // somewhat like your .always
    });
    

    Or, you can wrap your async functions to return your desired type of promise:

    function wrapFunc(arg) {
        return Parse.Promise.resolve(someAsyncFunc(arg))
    }
    

    And, then call the wrapper function instead.


    I'm not sure I recommend this because you are modifying the globally accessible Promise prototype which could affect other code, but you could also add a .always() to the built-in promise.

    Promise.prototype.always = function(fn) {
        return this.then(fn, fn);
    }
    

    Which is actually the exact same implementation that Parse uses. Then, you can use .always() on promises returned from async functions.