user762579
user762579

Reputation:

ES6 JS Promises - how to avoid conditional nesting

I am trying to write a piece of code using promises, avoiding nesting them but I am stuck in testing the returned results to handle the promises flow .. Is this pattern workable ??

// set of promise tasks returning values

    function doTask1() => {
        return apiPromise1()
         .then((result1) => {
             return result1;
         })
    }
    function doTask2(result1, paramOne) => {
        return apiPromise2(result1, paramOne)
         .then((result2) => {
             return result2;
         })
    }
    function doTask3(result1) => {
        return apiPromise3()
         .then((result3) => {
             return result3;
         })
    }
    function doTask4(result1, paramOne) => {
        return apiPromise4()
         .then((result4) => {
             return result4;
         })
    }

// main promise to handle the flow of promises according to promises returned results

    function getCurrentProcess(paramOne) {
        const promises = [];

   // how to get the returned result1 to be used by other promises ?
        promises.push(doTask1); 
        if (result1 === 'OK') {
            promises.push(doTask2(result1, paramOne));

            if (result2 === 'OK') {
                promises.push(doTask3(result1));

                if (result3 === 'OK') {
                    promises.push(doTask4(result1, paramOne));
                }
            }
        }

        return Promisz.all(promises)
        .then(() => {
            return 'well done'
        });

    }

// initial calling function

    exports.newJob = functions.https.onRequest((req, res) => {
      const paramOne = { ... }
      getCurrentProcess(paramOne).then((res) => {
        return { status: 200, infos: res };
      }, error => {
        return {status: error.status, infos: error.message};
      }).then(response => {
        return res.send(response);
      }).catch(console.error);
    });

Upvotes: 2

Views: 234

Answers (5)

user762579
user762579

Reputation:

Thanks to all feedbacks !!

All answers are rights... however I voted for CodingIntrigue wtapprer function solution in my case...

1 - As i am using Firebase functions , it's still ES5 , I cannot use sync/await. Using babel or typescript only for Firebase functions will result in much more setup work...

2 - I tested various use cases and this pattern is quite easy to understand with JS level... I am sur that it can be improved later..

so finally I got this running ...

    let auth = null;
    let myList = null;

    const conditional = (...fns) => {
      if(fns.length === 0) return Promise.resolve();
      const [next] = fns;
      return next()
        .then(result => {
          if(result) {
            return conditional(...fns.slice(1));
          }
          return result;
        });
    };

    const task1 = (param1) => Promise.resolve()
        .then(() => {
        console.log('TASK1 executed with params: ', param1)
        auth = "authObject"
          return true;
        });

    const task2 = (param1, param2) => Promise.resolve()
        .then(() => {
        console.log('TASK2 executed with params: ', param1, param2)
          return true;
        });

    const task3 = (param1, param2) => Promise.resolve()
        .then(() => {
        console.log('TASK3 executed with params: ', param1, param2)
        myList = "myListObject"
        console.log('search for param2 in myList...')
        console.log('param2 is NOT in myList task4 will not be executed')
          return false;
        });

    const task4 = (param1) => Promise.resolve()
        .then(() => {
        console.log('TASK4 executed with params: ', param1)
          return true;
        });

    // FIREBASE HTTP FUNCTIONS ==================
    exports.newContactMessage = functions.https.onRequest((req, res) => {
      conditional(() => task1("senderObject"), () => task2(auth, "senderObject"), () => task3(auth, "senderObject"), () => task4("senderObject"))
        .then((res) => {
        return { status: 200, infos: res };
      }, error => {
        return {status: error.status, infos: error.message};
      }).then(response => {
        return res.send(response);
      }).catch(console.error);
    });

Upvotes: 0

Estus Flask
Estus Flask

Reputation: 222493

 .then((result1) => {
     return result1;
 })

is a no-op and should be omitted, but I suppose that real code doesn't have this problem.

This is a use case for async function because they can seamlessly handle this sort of control flow, as another answer suggests. But since async is syntactic sugar for raw promises, it can be written in ES6. Since tasks depend on results of each other, they cannot be processed with Promise.all.

This is same case as this one that uses async.

You can bail out from promise chain by throwing an exception and avoid nested conditions with:

// should be additionally handled if the code is transpiled to ES5
class NoResultError extends Error {}

function getCurrentProcess(paramOne) {
    doTask1()
    .then(result1 => {
      if (result1 !== 'OK') throw new NoResultError(1);
      return result1;
    })
    .then(result1 => ({ result1, result2: doTask2(result1, paramOne) }))
    .then(({ result1, result2 }) => {
      if (result2 !== 'OK') throw new NoResultError(2);
      return result1;
    })
    // etc
    .then(() => {
        return 'well done';
    })
    .catch(err => {
      if (err instanceof NoResultError) return 'no result';
      throw err;
    })
}

Since result1 is used in multiple then callbacks, it could be saved to a variable instead of being passed through promise chain.

Promise chain could become simpler if NoResultErrors were thrown in task functions.

Upvotes: 0

CodingIntrigue
CodingIntrigue

Reputation: 78535

You could write a wrapper function which takes an array of doTaskN as deferred functions:

const conditional = (...fns) => {
  if(fns.length === 0) return Promise.resolve();
  const [next] = fns;
  return next()
    .then(() => conditional(...fns.slice(1)));
};

The idea would be to pass in the reference to the doTask functions so that the conditional function executes them. This can be used like:

conditional(doTask1, doTask2, doTask3, doTask4)
    .then(() => {
      console.log("all done");
    })
    .catch(() => {
      console.log("failed");
    });

Here's a full example of how to use it:

const conditional = (...fns) => {
  if(fns.length === 0) return Promise.resolve();
  const [next] = fns;
  return next()
  	.then(result => {
      console.log("task:", result);
      if(result === "OK") {
        return conditional(...fns.slice(1))
      }
    });
};

const task1 = (param1, param2) => Promise.resolve("OK");

const task2 = (param1) => Promise.resolve("OK");

const task3 = () => Promise.resolve("failed");

const task4 = () => Promise.resolve("OK");

conditional(() => task1("one", 2), () => task2(1), task3, task4)
	.then(() => {
      console.log("all done");
    })
	.catch(() => {
      console.log("failed");
    });

Upvotes: 0

Aik
Aik

Reputation: 3738

If you want to write promises in more procedural way you need use async/await (ES6). If you need backward compatibility with ES5 you need to use babel or typescript which translate await/async to ES5.

async function getCurrentProcess(paramOne) {
    const result1 = await doTask1(); 
    if (result1 === 'OK') {
        const result2 = await doTask2(result1, paramOne);

        if (result2 === 'OK') {
            const result3 = await doTask3(result1);

            if (result3 === 'OK') {
                await doTask4(result1, paramOne);
            }
        }
    }

    return 'well done'

}

Without async/await you need to use promise chain:

doTask1().then((result1)=>{
   if (result1 === 'OK') {
      ...
   }
   ...
})

However it will not produce readable code.

Upvotes: 1

Tomislav
Tomislav

Reputation: 752

If you want that your promise return result is used by other promises, you shouldn't use Promise.all() method because it doesn't run methods in the order you want, it just waits for all of the promise methods to complete and returns all results.

Maybe something like promise-array-runner would help?

Maybe you could check if result === 'OK' inside your task method? Or create a Factory which takes care of that.

Upvotes: 0

Related Questions