3

I saw promise implementation in Handling multiple catches in promise chain which produce a very readable chain

return validateInput
                 .then(checkLoginPermission)
                 .then(checkDisableUser)
                 .then(changePassword);

However, in order to do this each function needs to return a value instead of a Promise? Since Promise can resolves to either value or a Promise so this is not a problem. My goal is to turn every function to have readable clear logic as such.

The problem occurs when trying to unwind the nested promise function

return validateInput
       .then(function(resultA) {
            return checkLoginPermission
               .then (function(resultB) {
                   // Do something with resultA
               })
       });

Imagine the original implementation involves accessing the value from previous promise. With nested promise, it is easily achievable. But with flatten chain, I would need to break up each function like this

function validateInput = function (resultA ) {
        return Promise.resolve({resultA : resultA, resultB : 
}
function checkLoginPermission = function (mix ) {
        let resultA = mix.resultA;
        let resultB = mix.resultB
        //Do something with resultA
        ... 
}

This is worse when the last function in the chain rely on something from the very beginning. That means the value have to be passed down from the beginning of the chain even if it was not used.

So am I accidentally stepping on some kind of anti-pattern that might affect performance? How else can I achieve good readability without all these hassles?

Community
  • 1
  • 1
Zanko
  • 4,298
  • 4
  • 31
  • 54

4 Answers4

4

This is actually where async and await come in. It's good when you need results across multiple asynchronous calls/promises to be in scope. If you can use that, I'd say try it.

async function foo () {
    const input = await validateInput()
    const hasPermission = await checkLoginPermission(input)
    const result = await checkDisableUser(hasPermission)
    return await changePassword(result)
}

Just pass the variables into what function as they need to be. Just showing an example there. I was also a bit unsure of how you're setting validateInput, i think you need to put await infront of the function call itself.

If you cannot use async/await, I usually go with your 2nd code snippet, or define the higher scope variables ontop:

let resultA
return validateInput
   .then(function(result) {
        resultA = result
        return checkLoginPermission
           .then (function(resultB) {
               // Do something with resultA
           })
   });
agmcleod
  • 13,321
  • 13
  • 57
  • 96
1

Promises are pattern, related to functional programming, there direct passing data from one function to other is a basic (it's called compose, here examples: http://scott.sauyet.com/Javascript/Talk/Compose/2013-05-22/). So it's not anti-pattern by no means.

I can't see any problem in such pattern. You can pass any data you want to next Promises and in nested Promises grab what they need. It's preety transparent and clear:

function validateInput() {
    return Promise.resolve({resultA: 1});
}

function checkLoginPermission(result) {
    return new Promise(function(resolve, reject) {
        // ...
        // code
        // ...
        result.resultB = 2;
        return resolve(result);
    });
}

function checkDisableUser(result) {
    return new Promise(function(resolve, reject) {
        // grab some data from previous function
        let resultB = result.resultB;
        // ...
        // code
        // ...
        result.resultC = 3;
        return resolve(result);
    });
}

function changePassword(result) {
    return new Promise(function(resolve, reject) {
        // grab some data from previous functions
        let resultB = result.resultB;
        let resultC = result.resultC;
        // ...
        // code
        // ...
        result.resultD = resultB * resultC;
        return resolve(result);
    });
}

validateInput()
    .then(checkLoginPermission)
    .then(checkDisableUser)
    .then(changePassword);

Also you can collect data in some variable, declared before Promises and so you will not have to pass result. But it will destroy functional nature of Promises.

rie
  • 286
  • 2
  • 5
0

The inner .then(/* ... */) callbacks can return either a primitive value or a Promise that resolves to some value. If it is another promise then the next .then won't start until the inner promise is resolved. Essentially, Promises always resolve to a non-promise type. If you resolve or return another Promise, it will be automatically unwrapped.

William B
  • 1,411
  • 8
  • 10
-1

I would like to propose a solution using ramda.js#pipeP().

The good thing about this function is that it resolves promises sequentially.

We can rewrite your example using pipeP():

import pipeP from 'ramda/src/pipeP'

pipeP([
  checkLoginPermission,
  checkDisableUser,
  changePassword
])(initialValue)
.then(responseChangePassword => { ... })

The results of a previous promise are passed to the following one.

Amio.io
  • 20,677
  • 15
  • 82
  • 117