17

Using the Promises design pattern, is it possible to implement the following:

 var a, promise

 if promise.resolve
     a = promise.responsevalue;

 if promise.reject
     a = "failed"

 AFTER resolution/rejection. Not ASYNC!!
     send a somewhere, but not asynchronously. //Not a promise

What I'm looking for is something like finally in a try - catch situation.

PS: I'm using the ES6 Promise polyfill on NodeJS

nikjohn
  • 16,079
  • 9
  • 45
  • 78
  • Is this pure ES6? See here: http://stackoverflow.com/questions/38575561/javascript-promise-single-callback-for-fulfillment-or-rejection#comment64540579_38575561 – Zach Aug 08 '16 at 13:20
  • I'm using the ES6 Promise polyfill on NodeJS – nikjohn Aug 08 '16 at 13:22
  • Possible duplicate of [ES6 promise settled callback?](http://stackoverflow.com/questions/32362057/es6-promise-settled-callback) – Zach Aug 08 '16 at 13:23
  • 2
    `promise.then(function success(responseValue ) { return responseValue; } , function error() { return "failed"; }).then(function() { //This will work as finally});` – Olivier Boissé Aug 08 '16 at 13:24
  • You can not check the status or result of a promise like that. Once you receive a promise object at hand, you can only call either it's `then` or `catch` method and provide appropriate callbacks to be invoked whenever later in time your promise resolves or rejects.. – Redu Aug 08 '16 at 14:26

2 Answers2

17

NOTE: finally is now a standard part of JavaScript's promises, so you'd do this:

thePromise.then(result => doSomething(result)
          .catch(error => handleOrReportError(error))
          .finally(() => doSomethingAfterFulfillmentOrRejection());

Answer from before finally was standard:


If you return a value from catch, then you can just use then on the result of catch.

thePromise.then(result => doSomething(result)
          .catch(error => handleErrorAndReturnSomething(error))
          .then(resultOrReturnFromCatch => /* ... */);

...but that means you're converting a rejection into a fulfillment (by returning something from catch rather than throwing or returning a rejected promise), and relies on that fact.


If you want something that transparently passes along the fulfillment/rejection without modifying it, there's nothing built into ES2015 ("ES6") promises that does that (edit: again, there is now), but it's easy to write (this is in ES2015, but I have an ES5 translation below):

{
    let worker = (p, f, done) => {
        return p.constructor.resolve(f()).then(done, done);
    };
    Object.defineProperty(Promise.prototype, "finally", {
        value(f) {
            return this.then(
                result => worker(this, f, () => result),
                error  => worker(this, f, () => { throw error; })
            );
        }
    });
}

Example:

{
  let worker = (p, f, done) => {
    return p.constructor.resolve(f()).then(done, done);
  };
  Object.defineProperty(Promise.prototype, "finally", {
    value(f) {
      return this.then(
        result => worker(this, f, () => result),
        error  => worker(this, f, () => { throw error; })
      );
    }
  });
}
test("p1", Promise.resolve("good")).finally(
  () => {
    test("p2", Promise.reject("bad"));
  }
);
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .finally(() => {
    console.log(name, "in finally");
  })
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}

A couple of notes on that:

  1. Note the use of this.constructor so that we're calling resolve on whatever kind of promise (including a possible subclass) created the original promise; this is consistent with how Promise.resolve and others work, and is an important part of supporting subclassed promises.

  2. The above is intentionally not including any argument to the finally callback, and no indication of whether the promise was fulfilled or rejected, in order to be consistent with finally in the classic try-catch-finally structure. But if one wanted, one could easily pass some of that information into the callback.

  3. Similarly, the above does not use the value returned by the finally callback except that if it's a promise, it waits for the promise to settle before allowing the chain to continue.

Here's the ES5 translation of that:

(function() {
    function worker(ctor, f, done) {
        return ctor.resolve(f()).then(done, done);
    }
    Object.defineProperty(Promise.prototype, "finally", {
        value: function(f) {
            var ctor = this.constructor;
            return this.then(
                function(result) {
                    return worker(ctor, f, function() {
                        return result;
                    });
                },
                function(error) {
                    return worker(ctor, f, function() {
                        throw error;
                    });
                }
            );
        }
    });
})();

Example:

(function() {
  function worker(ctor, f, done) {
    return ctor.resolve(f()).then(done, done);
  }
  Object.defineProperty(Promise.prototype, "finally", {
    value: function(f) {
      var ctor = this.constructor;
      return this.then(
        function(result) {
          return worker(ctor, f, function() {
            return result;
          });
        },
        function(error) {
          return worker(ctor, f, function() {
            throw error;
          });
        }
      );
    }
  });
})();

test("p1", Promise.resolve("good")).finally(function() {
  test("p2", Promise.reject("bad"));
});

function test(name, p) {
  return p.then(
      function(result) {
        console.log(name, "initial fulfillment:", result);
        return result;
      },
      function(error) {
        console.log(name, "initial rejection; propagating it");
        throw error;
      }
    )
    .finally(function() {
      console.log(name, "in finally");
    })
    .then(
      function(result) {
        console.log(name, "fulfilled:", result);
      },
      function(error) {
        console.log(name, "rejected:", error);
      }
    );
}

I think this is the simplest way to integrate this functionality into a Promise polyfill in ES5.


Or if you prefer to subclass Promise rather than modifying its prototype:

let PromiseX = (() => {
    let worker = (p, f, done) => {
        return p.constructor.resolve(f()).then(done, done);
    };
    class PromiseX extends Promise {
        finally(f) {
            return this.then(
                result => worker(this, f, () => result),
                error  => worker(this, f, () => { throw error; })
            );
        }
    }
    PromiseX.resolve = Promise.resolve;
    PromiseX.reject = Promise.reject;

    return PromiseX;
})();

Example:

let PromiseX = (() => {
  let worker = (p, f, done) => {
    return p.constructor.resolve(f()).then(done, done);
  };
  class PromiseX extends Promise {
    finally(f) {
      return this.then(
        result => worker(this, f, () => result),
        error  => worker(this, f, () => { throw error; })
      );
    }
  }
  PromiseX.resolve = Promise.resolve;
  PromiseX.reject = Promise.reject;

  return PromiseX;
})();

test("p1", PromiseX.resolve("good")).finally(
  () => {
    test("p2", PromiseX.reject("bad"));
  }
);
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .finally(() => {
    console.log(name, "in finally");
  })
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}

You've said you want to do it without either extending the Promise.prototype or subclassing. In ES5, a utility function would be extremely awkward to use, because you'd have to pass it the promise to act on, which would be completely out of step with normal promise usage. In ES2015, it's possible to do something more natural but it's still more of a pain to call than either modifying the prototype or subclassing:

let always = (() => {
    let worker = (f, done) => {
        return Promise.resolve(f()).then(done, done);
    };
    return function always(f) {
        return [
            result => worker(f, () => result),
            error  => worker(f, () => { throw error; })
        ];
    }
})();

Usage:

thePromise.then(...always(/*..your function..*/)).

Note the use of the spread operator (which is why this won't work in ES5), so always can supply both arguments to then.

Example:

let always = (() => {
  let worker = (f, done) => {
    return Promise.resolve(f()).then(done, done);
  };
  return function always(f) {
    return [
      result => worker(f, () => result),
      error  => worker(f, () => { throw error; })
    ];
  }
})();

test("p1", Promise.resolve("good")).then(...always(
  () => {
    test("p2", Promise.reject("bad"));
  }
));
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .then(...always(() => {
    console.log(name, "in finally");
  }))
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}

In the comments you expressed a concern that the finally wouldn't wait for the promise; here's that last always example again, with delays to demonstrate that it does:

let always = (() => {
  let worker = (f, done) => {
    return Promise.resolve(f()).then(done, done);
  };
  return function always(f) {
    return [
      result => worker(f, () => result),
      error  => worker(f, () => { throw error; })
    ];
  }
})();

test("p1", 500, false, "good").then(...always(
  () => {
    test("p2", 500, true, "bad");
  }
));

function test(name, delay, fail, value) {
  // Make our test promise
  let p = new Promise((resolve, reject) => {
    console.log(name, `created with ${delay}ms delay before settling`);
    setTimeout(() => {
      if (fail) {
        console.log(name, "rejecting");
        reject(value);
      } else {
        console.log(name, "fulfilling");
        resolve(value);
      }
    }, delay);
  });

  // Use it
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .then(...always(() => {
    console.log(name, "in finally");
  }))
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}
T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • @tj Wouldn't this execute `resultOrReturnFromCatch` asynchronously? As in, if there's a 5 sec delay in the original promise resolution, wouldn't `resultOrReturnFromCatch` just go ahead and execute anyway? – nikjohn Aug 08 '16 at 13:55
  • @ChanandlerBong: No, why would it? BTW, I've added a utility function version to the end, so you don't have to extend prototypes of subclass. – T.J. Crowder Aug 08 '16 at 13:59
  • @ChanandlerBong: It occurs to me that you've said you're using a Promise polyfill and "would love" to use ES6, suggesting that you're not using it now. So all of these answers with arrow functions (and the spread operator in my case) are of...limited utility. :-) I've added an ES5 version of the prototype extension above. I think that's the most reasonable way to approach this in ES5. The `always` utility function is basically impossible to reasonably write in ES5, because of the need to supply both arguments to `then` (hence the spread operator). – T.J. Crowder Aug 08 '16 at 14:22
3

ES2015 code:

promise.then(val => val).catch(() => "failed").then(a => doSomethigWithA(a));
T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
Maxx
  • 1,600
  • 7
  • 16
  • 1
    `promise.then(val => val)` isn't useful. – Ry- Aug 08 '16 at 13:29
  • 1
    @Ryan it's just for example for this question – Maxx Aug 08 '16 at 13:31
  • Wouldn't this execute `doSomethignWithA(a)` asynchronously? As in, if there's a 5 sec delay in the original promise resolution, wouldn't `doSomethingWithA(a)` just go ahead and execute anyway? – nikjohn Aug 08 '16 at 13:40
  • @ChanandlerBong if i understand question right, then yes, `doSomethingWithA` will run after original `promise` only. – Maxx Aug 08 '16 at 13:55
  • I tried it and added a 5 sec delay. It goes ahead and just executes `doSomethingWithA` anyway. So this is async. I'm looking for a sync solution – nikjohn Aug 08 '16 at 13:58
  • @ChanandlerBong i can't answer you to your question without example of some code. Maybe your delay code works not like you think it should. – Maxx Aug 08 '16 at 14:09
  • @ChanandlerBong: The above won't run `doSomethingWithA` until the promise `.then(a => doSomethingWithA(a))` is called on settles. – T.J. Crowder Aug 08 '16 at 14:09