Proposal: promise.if/when
See original GitHub issueMotivation
I often find myself and other doing conditional chaining, where I create a initial promise and then keep conditionally adding additional actions to it. For me personally this is a somewhat tiring task, it also feels hard to write and to read, therefor I decided to write something to make my live easier.
It supports instant conditional chaining eg. perform the chaining directly when the chain is created
and deferred conditional chaining check the condition when the previous promise resolved
.
A simple implementation and example usage would be as follows:
'use strict';
const Promise = require('bluebird');
function utilCallIfFuncOrReturn (func, val) {
if (typeof func === 'function') {
return func(val);
}
return func;
}
/**
* Conditionally chains promises.
* @param {Function|* => *} predicate If function, the predicate will be invoked with the resolve
* value of the previous promise.
* If anything else, the truthness of the value will be used to immediately decide when to chain
* the promises or not.
*
* @param {Function|* => Promise|*} truthy If function, thruthy will be invoked with the resolved value of the
* previous promise, the returned value will be inserted into the promise chain.
* If anything else, the value will be inserted into the promise chain.
* This will only be invoked if the predicate was truthy.
*
* @param {Function|* => Promise|*} falsy If function, falsy will be invoked with the resolved value of the
* previous promise, the returned value will be inserted into the promise chain.
* If anything else, the value will be inserted into the promise chain.
* This will only be invoked if the predicate was false.
* @return {Promise.<*>}
*/
Promise.prototype.if = Promise.prototype.when = function (predicate, truthy, falsy) {
if (typeof predicate === 'function') {
return this.then(val => {
if (predicate(val)) {
return utilCallIfFuncOrReturn(truthy, val);
}
if (falsy) {
return utilCallIfFuncOrReturn(falsy, val);
}
return val;
})
} else if (predicate) {
return this.then(val => utilCallIfFuncOrReturn(truthy, val));
} else if (falsy) {
return this.then(val => utilCallIfFuncOrReturn(falsy, val));
}
return this;
}
function someThingConditionally (val) {
return val + 1;
}
function someThingWithDeferredConditionCheck (val) {
return val - 1;
}
{
const a = 1;
const chain = Promise.resolve(1)
.if(a === 1, someThingConditionally)
.if(val => val % 2 === 0, someThingWithDeferredConditionCheck)
.if(val => val === 3, () => { throw Error('I shouldn\'t be running'); })
.then(val => console.log(1, val))
}
// vs
{
const a = 1;
let chain = Promise.resolve(1)
if (a === 1) {
chain = chain.then(someThingConditionally)
}
chain = chain.then(val => {
if (val % 2 === 0) {
return someThingWithDeferredConditionCheck(val);
}
return val;
})
chain = chain.then(val => {
if (val === 3) {
throw Error('I shouldn\'t be running');
}
return val;
})
.then(val => console.log(2, val))
}
As you can see the code is significantly more compact and (personally) easier to read/comprehend, there is also no significant performance overhead.
About the method name
I personally prefer if
as it is very concise, but when
would also be acceptable.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:6
- Comments:6 (2 by maintainers)
Depends entirely on the use case, both moments might be appropriate. But checking in before is just as easy:
There is also a third alternative:
Checking the condition before the promise is resolved is confusing and error-prone, so if we leave that out:
Alternative implementation, using a combinator
Usage of alternative implementation: