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.
I personally prefer if as it is very concise, but when would also be acceptable.
There is also a third alternative:
const a = 1;
let chain = Promise.resolve(1)
if (a === 1) { chain = chain.then(someThingConditionally) }
chain = chain
.then(val => val % 2 === 0 ? someThingWithDeferredConditionCheck(val) : val)
.then(val => val === 3 ? Promise.reject(Error('I shouldn\'t be running')) : val)
.then(val => console.log(2, val))
}
Checking the condition before the promise is resolved is confusing and error-prone, so if we leave that out:
const a = 1;
const chain = Promise.resolve(1)
.then(val => a === 1 ? someThingConditionally(val) : val)
.then(val => val % 2 === 0 ? someThingWithDeferredConditionCheck(val) : val)
.then(val => val === 3 ? Promise.reject(Error('I shouldn\'t be running')) : val)
.then(val => console.log(2, val))
Alternative implementation, using a combinator
let when = (condition, handle, handleElse) => val => {
if (typeof condition === 'function') { condition = condition(val); }
if (condition) { return handle(val); }
if (handleElse != null) { return handleElse(val); }
return val;
}
Usage of alternative implementation:
const a = 1;
const chain = Promise.resolve(1)
.then(when(a === 1, someThingConditionally))
.then(when(val => val % 2 === 0, someThingWithDeferredConditionCheck))
.then(when(val => val === 3, () => { throw Error('I shouldn\'t be running'); }))
.then(val => console.log(1, val))
Checking the condition before the promise is resolved is confusing and error-prone
Depends entirely on the use case, both moments might be appropriate. But checking in before is just as easy:
Promise.resolve(1)
.then(a === 1 ? someThingConditionally : null)
.then(val => val % 2 === 0 ? someThingWithDeferredConditionCheck(val) : val)
.then(val => val === 3 ? Promise.reject(Error('I shouldn\'t be running')) : val)
.then(val => console.log(2, val))
I say we vote. I vote no. @petkaantonov @spion ?
I am -1 as well
Afraid to say I'm against it as well: low overhead elimination compared to the most compact alternative
(the ternary operator version), is also easily implemented as a combinator.
Sorry
Most helpful comment
Depends entirely on the use case, both moments might be appropriate. But checking in before is just as easy: