Promises: Execute something regardless of resolve/reject?

12,329

Solution 1

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);
    }
  );
}

Solution 2

ES2015 code:

promise.then(val => val).catch(() => "failed").then(a => doSomethigWithA(a));
Share:
12,329
nikjohn
Author by

nikjohn

Updated on June 15, 2022

Comments

  • nikjohn
    nikjohn about 2 years

    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

  • Ry-
    Ry- almost 8 years
    promise.then(val => val) isn't useful.
  • Maxx
    Maxx almost 8 years
    @Ryan it's just for example for this question
  • nikjohn
    nikjohn almost 8 years
    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
    nikjohn almost 8 years
    @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?
  • Maxx
    Maxx almost 8 years
    @ChanandlerBong if i understand question right, then yes, doSomethingWithA will run after original promise only.
  • nikjohn
    nikjohn almost 8 years
    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
  • T.J. Crowder
    T.J. Crowder almost 8 years
    @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.
  • Maxx
    Maxx almost 8 years
    @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.
  • T.J. Crowder
    T.J. Crowder almost 8 years
    @ChanandlerBong: The above won't run doSomethingWithA until the promise .then(a => doSomethingWithA(a)) is called on settles.
  • T.J. Crowder
    T.J. Crowder almost 8 years
    @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).