Promises: Execute something regardless of resolve/reject?
Asked Answered
P

2

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

Ptarmigan answered 8/8, 2016 at 13:19 Comment(5)
Is this pure ES6? See here: #38576061Invocation
I'm using the ES6 Promise polyfill on NodeJSPtarmigan
Possible duplicate of ES6 promise settled callback?Invocation
promise.then(function success(responseValue ) { return responseValue; } , function error() { return "failed"; }).then(function() { //This will work as finally});Westernism
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..Backup
B
18

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);
    }
  );
}
Burgett answered 8/8, 2016 at 13:25 Comment(3)
@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?Ptarmigan
@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.Burgett
@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).Burgett
P
3

ES2015 code:

promise.then(val => val).catch(() => "failed").then(a => doSomethigWithA(a));
Pulmonate answered 8/8, 2016 at 13:27 Comment(7)
promise.then(val => val) isn't useful.Assentation
@Ryan it's just for example for this questionPulmonate
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?Ptarmigan
@ChanandlerBong if i understand question right, then yes, doSomethingWithA will run after original promise only.Pulmonate
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 solutionPtarmigan
@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.Pulmonate
@ChanandlerBong: The above won't run doSomethingWithA until the promise .then(a => doSomethingWithA(a)) is called on settles.Burgett

© 2022 - 2024 — McMap. All rights reserved.