Promises: Execute something regardless of resolve/reject?
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:
Note the use of
this.constructor
so that we're callingresolve
on whatever kind of promise (including a possible subclass) created the original promise; this is consistent with howPromise.resolve
and others work, and is an important part of supporting subclassed promises.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 withfinally
in the classictry-catch-finally
structure. But if one wanted, one could easily pass some of that information into the callback.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));
![nikjohn](https://i.stack.imgur.com/zPziX.jpg?s=256&g=1)
nikjohn
Updated on June 15, 2022Comments
-
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 atry - catch
situation.PS: I'm using the ES6 Promise polyfill on NodeJS
-
Ry- almost 8 years
promise.then(val => val)
isn't useful. -
Maxx almost 8 years@Ryan it's just for example for this question
-
nikjohn almost 8 yearsWouldn't this execute
doSomethignWithA(a)
asynchronously? As in, if there's a 5 sec delay in the original promise resolution, wouldn'tdoSomethingWithA(a)
just go ahead and execute anyway? -
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'tresultOrReturnFromCatch
just go ahead and execute anyway? -
Maxx almost 8 years@ChanandlerBong if i understand question right, then yes,
doSomethingWithA
will run after originalpromise
only. -
nikjohn almost 8 yearsI 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 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 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 almost 8 years@ChanandlerBong: The above won't run
doSomethingWithA
until the promise.then(a => doSomethingWithA(a))
is called on settles. -
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 tothen
(hence the spread operator).