ES6 promise settled callback?
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?
Solution 1:
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.
Solution 2:
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 await
ing 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.)
Solution 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
});