question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Proposal: promise.if/when

See original GitHub issue

Motivation

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:closed
  • Created 7 years ago
  • Reactions:6
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

3reactions
berguscommented, Mar 30, 2016

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))
1reaction
spioncommented, Mar 29, 2016

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))
Read more comments on GitHub >

github_iconTop Results From Across the Web

promises-aplus/promises-spec: An open standard for ...
If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then . then must return...
Read more >
LECTURE VIII. - CONTRACT. II. ELEMENTS
It is a promise and a term of a binding contract as soon as it is anything. An offer is a revocable and...
Read more >
Contracts, Promises and the Law of Obligations | Oxford
When the offer is accepted, the agreement is consummated, and a contract comes into existence before anything is actually done by the parties....
Read more >
THE KINDS OF PROMISES LEGALLY ENFORCED
the terms proposed, the plaintiff was not to pay his debt to his creditors, but ... If, when a man promised to labour...
Read more >
CONTRACTS REVIEW
RULE: A party who relies on a promise that a contract is forthcoming may recover under a PE theory. It is not necessary...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found