Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a 'finally' pattern for Promises?

Imagine the following Promise-based example:

function treadLightly() {
  return Promise.resolve()
    .then(function() { allocateResource(); })
    .then(function() { doRiskyOperation(); })
    .then(function() { releaseResource(); })
    .catch(function() { releaseResource(); })
  ;
}

We want to call releaseResource() whether or not doRiskyOperation() resolves or rejects. But there's a whiff of code smell in calling releaseResource() in two separate places.

What we really want is something equivalent to javascript's finally.

Is there a cleaner way to code this in Promises?

like image 559
fearless_fool Avatar asked Dec 24 '22 22:12

fearless_fool


2 Answers

ES2015 (ES6) promises don't have finally yet. There's a Stage 2 proposal for it, which means there's basically no chance of it being in ES2017 but some possibility of it being in ES2018. I haven't seen a really good pattern (as opposed to an actual finally feature) for it.

Some third-party promise libraries have it, including Bluebird, Q, and when. jQuery's Deferred's promises also have it, in the form of always.

The proposal has a polyfill you can use:

if (typeof Promise !== 'function') {
    throw new TypeError('A global Promise is required');
}

if (typeof Promise.prototype.finally !== 'function') {
    var speciesConstructor = function (O, defaultConstructor) {
        var C = typeof O.constructor === 'undefined' ? defaultConstructor : O.constructor;
        var S = C[Symbol.species];
        return S == null ? defaultConstructor : S;

        var C = O.constructor;
        if (typeof C === 'undefined') {
            return defaultConstructor;
        }
        if (!C || (typeof C !== 'object' && typeof C !== 'function')) {
            throw new TypeError('O.constructor is not an Object');
        }
        var S = C[Symbol.species];
        if (S == null) {
            return defaultConstructor;
        }
        if (typeof S === 'function' && S.prototype) {
            return S;
        }
        throw new TypeError('no constructor found');
    };
    var shim = {
        finally(onFinally) {
            var handler = typeof onFinally === 'function' ? onFinally : () => {};
            var C;
            var newPromise = Promise.prototype.then.call(
                this, // throw if IsPromise(this) is not true
                x => new C(resolve => resolve(handler())).then(() => x),
                e => new C(resolve => resolve(handler())).then(() => { throw e; })
            );
            C = speciesConstructor(this, Promise); // throws if SpeciesConstructor throws
            return newPromise;
        }
    };
    Promise.prototype.finally = shim.finally;
}
like image 113
T.J. Crowder Avatar answered Dec 26 '22 11:12

T.J. Crowder


Although @T.J. Crowder's answer is correct, a practical solution to your issue can be adding another .then at the end:

function treadLightly() {
  return Promise.resolve()
    .then(function() { allocateResource(); })
    .then(function() { doRiskyOperation(); })
    .catch(function() { /* Do nothing, or log the error */ })
    .then(function() { releaseResource(); })
  ;
}
like image 39
GilZ Avatar answered Dec 26 '22 12:12

GilZ