ES6 promise settled callback?
Asked Answered
S

8

45

I want to run the same action whether my Promise resolved successfully or not. I don't want to bind the same function to both args of .then. Isn't there a .always like jQuery has? If not, how do I achieve this?

Septi answered 2/9, 2015 at 20:1 Comment(4)
can't you do .finally(function(){...})?Oestrone
See #26668098Vermeil
@CharlieWynn It's undefined in Babel and not listed on MDN.Septi
You can try the when package npmjs.com/package/whenSpringy
A
37

Isn't there a .always like jQuery has?

No, there's not (yet). Though there is an active proposal, so maybe ES2018.
Yes, there is: promise .finally() is part of the standard since ES2018.

If not, how do I achieve this?

You can implement the finally method yourself like this:

Promise.prototype.finally = function(cb) {
    const res = () => this
    const fin = () => Promise.resolve(cb()).then(res)
    return this.then(fin, fin);
};

or more extensively, with passing resolution information to the callback:

Promise.prototype.finally = function(cb) {
    const res = () => this
    return this.then(value =>
        Promise.resolve(cb({state:"fulfilled", value})).then(res)
    , reason =>
        Promise.resolve(cb({state:"rejected", reason})).then(res)
    );
};

Both ensure that the original resolution is sustained (when there is no exception in the callback) and that promises are awaited.

Angelia answered 2/9, 2015 at 20:10 Comment(9)
I think error callback of the this.then in pollyfill should throw rather then return.Niko
@dfsq: It does by returning the original, rejected promise :-)Angelia
You are right, this is original promise because of =>, sure.Niko
Your first example has the same problem as described here doesn't it? I'm not sure about your "more extensive" solution though.Septi
@Mark: you mean that exceptions are swallowed? No, it does not, as I explained to dfsq :-) There is however a difference to the finally keyword, as you can return from a finally { } block but not from the promise .finally callback.Angelia
Rejecting is not the same as throwing. My understanding is finally should throw outside of the promise chain. E.g. .catch(e => setTimeout(() => { throw e; })).Jumbuck
@jib: rejecting is exactly the same in the context of promises. No, finally should not throw the exceptions outside of the promise chain, that's what .done() did, which is deprecated in favour of unhandled rejection detection.Angelia
Your first implementation (without the values) does appear to be in-line with the spec, so that's good :-)Septi
@riv No, that's a shorthand notation.Angelia
A
10

With async/await, you can a combination of await with try/finally, like so:

async function(somePromise) {
  try {
    await somePromise();
  } finally {
    // always run this-- even if `somePromise` threw something
  }
}

Here's a real example I have running in production with Node, using Babel's async-to-generator plugin.

// Wrap promisified function in a transaction block
export function transaction(func) {
  return db.sequelize.transaction().then(async t => {
    Sequelize.cls.set('transaction', t);
    try {
      await func();

    } finally {
      await t.rollback();
    }
  });
}

I use this code inside a mocha test alongside the Sequelize ORM to start a DB transaction, and regardless of the outcome of the DB calls within the test, always roll back at the end.

This is roughly analogous to Bluebird's .finally() method, but IMO, far nicer syntax!

(Note: In case you're wondering why I'm not awaiting on the first Promise- it's an implementation detail of Sequelize. It uses CLS to 'bind' a SQL transaction to a Promise chain. Anything that incurs inside the same chain is scoped to the transaction. Anything outside isn't. Therefore, awaiting on the Promise would have 'closed' the transaction block and broken the chain. I threw this example in to show you how 'vanilla' Promise handling can be mixed alongside async functions, and play well together.)

Affable answered 7/9, 2016 at 16:53 Comment(3)
Why aren't you using async function transaction and drop that then call as well?Angelia
Neat! Never thought about using try/finally with async/await. That'll come in handy.Septi
@Angelia - Sequelize uses something called 'CLS' to scope a transaction block to a Promise chain. If I used await, it would return a transaction handler but subsequent SQL would be outside of the block, and therefore not scoped to the transaction. It's an implementation detail of Sequelize.Affable
H
3

If you don't/can't update the prototype, the way to hack a finally is:

executeMyPromise()
.then(function(res){ return {res: res}; })
.catch(function(err){ return {err: err}; })
.then(function(data) {
    // do finally stuff
    if (data.err) {
        throw data.err;
    }
    return data.res;
}).catch(function(err) {
    // handle error
});
Haulm answered 11/6, 2017 at 11:19 Comment(1)
I don't think that's the same. See the 3rd and 4th points here. We can actually use Bergi's solution without modifying the prototype. You just have to call it funny: finallyFunc.call(thePromise, callback). Works well with the bind operator.Septi
C
1

Here is my implementation of .finally().

Promise.prototype.finally = function(cb) {
   return this.then(v=>Promise.resolve(cb(v)),
                    v=>Promise.reject(cb(v)));
};

I tested it:

(new Promise((resolve,reject)=>{resolve(5);})).finally(x=>console.log(x));  //5

(new Promise((resolve,reject)=>{reject(6);})).finally(x=>console.log(x));  //6

(new Promise((resolve,reject)=>{reject(7);}))
.then(x=>x,y=>y)
.catch(x=>{throw "error";}) 
.finally(x=>{console.log(x); throw "error"; return x;})  // 7
.then(x=>console.log(x),y=>console.log('e'));  //e
// Uncaught (in promise) undefined
Cecum answered 24/7, 2017 at 10:55 Comment(0)
E
1

Summary:

We now also have access to Promise.prototype.finally(). This is a function which can be put on the promise chain as a last element to perform some cleanup. it works the following way when compared to Promise.then and Promise.catch:

  • Promise.then only gets called when the promise is resolved (if you only put it the first argument callback function)
  • Promise.catch only gets called when the promise is rejected
  • Promise.finally always gets called when a promise is fullfilled, so both when the promise gets rejected or resolved.

Example:

let Prom = new Promise((res, rej) => {
  let random = Math.random();
  
  if (random > 0.5) {
    res(1);
  } else {
    rej('Error occured')
  }

  
});


Prom.then((val) => {
  console.log(val);
  return val * 10;
}).catch((err) => {
  console.log(err);
}).finally(() => {
  console.log('finally executed');
})

In the above exapmle we can observe that finally is always executed regardless if the promise resolves or rejects. Not that finally should ideally always be at the end of the promise chain to do some cleanup which should be executed regardless of Promise outcome.

The advantage of using finally is that it avoids the need for code duplication because it is executed for both a resolved and a rejected promise. Otherwise we would have to use hacks like:

.then(onfullfilled, onfullfilled)

or

.then(onfullfilled)
.catch(onfullfilled)

Note that now we have to define the onfullfilled function as a named function outside the promisehandler itself (or pass in 2 anonymous function copies which is even less elegant). Promise.finally solves this problem for us.

Exine answered 9/9, 2018 at 10:20 Comment(0)
P
0

No need to introduce new concepts

const promise = new Promise((resolve, reject) => {
  /*some code here*/
});

promise.then(() => {
  /* execute success code */
}, () => {
  /* execute failure code here */
}).then(() => {}, () => {}).then(() => {
  /* finally code here */
});
Piglet answered 3/12, 2017 at 19:5 Comment(1)
That would cause the return value from the first .then() branch to be discarded, wouldn't it?Loyola
R
0

To extend Bergi answer.

Returning Promise.reject() in catch handler will prevent finnalizing 'then' to be called.

So if you going to handle promise error 2+ times you should use boilerplate like this:

return myPromise()
.then(() => ... )
.catch((error) => {
  ...
  myFinnaly();
  return Promise.reject(error);
})
.then(() => myFinnaly());
Resignation answered 7/1, 2018 at 7:53 Comment(0)
O
0

allSettled works directly as finally:

Promise.allSettled([promiseSuccess, promiseReject])
  .then(results => console.log);

Check: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

Ophthalmologist answered 14/6, 2020 at 23:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.