Nodejs promises: how to concatenate the resolved data within the Promise.all function

10,302

Solution 1

If your promises are all taking the result of the previous promise and returning to the next (like they do in your example), you can make the syntax much terser by doing something like this:

function test1() {
  return new Promise(function(resolve, reject) {
    resolve("test1");
  });
}

function test2(p) {
  return new Promise(function(resolve, reject) {
    resolve(p + " test2");
  });
}

function test3(p) {
  return new Promise(function(resolve, reject) {
    resolve(p + " test3");
  });
}

test1()
  .then(test2)
  .then(test3)
  .then(console.log)

Solution 2

First of all, the code blocks that you proposed are not equivalent.

The "manual" way that you show says, "run test1, then test2 with the value returned from test1, then run test3 with the value returned from test2.

The Promise.all way will fire all three functions, effectively, simultaneously and will not feed the return value from one to the next (promise.waterfall).


You may want to try async functions. They provide a more terse, synchronous looking script.

async function test1() {
    return "test1";
}

async function test2(p) {
    return `test2[${p}]`;
}

async function test3(p) {
    return `test3[${p}]`;
}

(async function () {
    let t1 = await test1();
    console.log(`Look what I got: ${t1}`);
    let t2 = await test2(t1);
    console.log(`And here: ${t2}`);
    let t3 = await test3(t2);
    console.log(`Also here: ${t3}`);

    return {
        t1,
        t2,
        t3
    };
}()).then(console.log);

Running this gives you the output:

Look what I got: test1
And here: test2[test1]
Also here: test3[test2[test1]]
{ t1: 'test1', t2: 'test2[test1]', t3: 'test3[test2[test1]]' }

In response to guest271314's comment:

Why would a "Promise chain" need to be broken?

I see the need to break the chain if you need to use an earlier resolution in a later sequence, but don't want to pass a conglomeration of all the results in the chain down. You can either nest callbacks which kind of defeats the point of Promises, or you could use async functions.

Consider:

const test1 = () => Promise.resolve("test1"),
    test2 = (a) => Promise.resolve(`[${a}]test2`),
    test3 = (a) => Promise.resolve(`[${a}]test3`),
    test4 = (a) => Promise.resolve(`[${a}]test4`),
    test5 = (a) => Promise.resolve(`[${a}]test5`),
    greedy = (a, b) => Promise.resolve(`I need ${a} and ${b}`);

// Pass through
test1()
    .then(test2)
    .then(test3)
    .then(test4)
    .then(test5)
    // Greedy should actually be getting the result of test1 and test5
    .then(greedy)
    .then(console.log);

// "Breaking" the chain
test1()
    .then((a) => {
        return test2(a)
            .then(test3)
            .then(test4)
            .then(test5)
            .then((b) => {
                /*
                 * I have to nest if I want to give greedy
                 * the results of test1 and test5
                 */
                return greedy(a, b);
            });
    })
    .then(console.log);

// Using async
(async function () {
    const t1 = await test1(),
        t2 = await test2(t1),
        t3 = await test3(t2),
        t4 = await test4(t3),
        t5 = await test5(t4),
        // I already have t1 and t5 populated
        g = await greedy(t1, t5);
    return g;
}()).then(console.log);

Prints:

I need [[[[test1]test2]test3]test4]test5 and undefined
I need test1 and [[[[test1]test2]test3]test4]test5
I need test1 and [[[[test1]test2]test3]test4]test5
Share:
10,302
Admin
Author by

Admin

Updated on June 14, 2022

Comments

  • Admin
    Admin almost 2 years

    UPDATE: I FOUND AN "HOMEMADE" SOLUTION BY MY OWN, SCROLL DOWN TO THIS THIS SOLUTION!

    When I'm doing NodeJS promises "manually" I have the option to pass the resolve value from the parent promise to it's child, or more precisely, from the first promise to the second and so for and so on.

    So, after first promise is done, I can continue using it's resolved data in the next promise. Sometimes this data is crucial for the whole process. Such as sending a SMS via remote API, if the SMS will not be sent, I cannot be able to manage the next function.

    I have tried the "shortcut" (*sorry if it does not meant to be a shortcut): Promise.all. Well it's nice, and it pops up the catched promise reject value first and if everything is okay it goes to finish. But how can I preserve the resolved data in the same manner I have described above?

    I will push here some code, so we can understand each other better:

    Trying to do this using Promise.all

    function test1() {
        return new Promise(function(resolve, reject) {
          resolve("test1");
        });
      }
    
      function test2() {
        return new Promise(function(resolve, reject) {
          resolve("test2");
        });
      }
    
      function test3() {
        return new Promise(function(resolve, reject) {
          resolve("test3");
        });
      }
    
      Promise.all([test1, test2, test3].map(func => func())).then((result) => {
        console.log('result: ', result);
      }).catch((result) => {
        console.log("Catched: ", result);
      });

    Trying to do this using the "manual" way:

    function test1() {
        return new Promise(function(resolve, reject) {
          resolve("test1");
        });
      }
    
      function test2(p) {
        return new Promise(function(resolve, reject) {
          resolve("test2");
        });
      }
    
      function test3(p) {
        return new Promise(function(resolve, reject) {
          resolve("test3");
        });
      }
    
      test1().then(function(result) {
        return test2(result);
      }).then(function(result) {
        return test3();
      }).then(function(result) {
        console.log("Finished");
      });

    Now for the catch section if I am using the Promise.all function with the catch call back, it will rise the first catched match. This is not good. Since I want to first tell the user he forgot filling email, later he has to be notified whether there was a problem sending SMS. But it does not working like that.

    Actually, I can do it the manual way seperate for then and seperate for catch, but it is a mess.. And it is very easy to cause mistake in the syntax that will take hours to find.

    ********************** UPDATE - I FOUND AN HOMEMADE SOLUTIN!!! **********************

    function test1() {
        // some process return in overall as boolean
        var overall = true;
        if (overall)
          return test2({test1: "is resolved"});
        return catched("rejected on 1");
      }
    
      function test2(result) {
        // some process return in overall as boolean
        var overall = true;
        result.test2 = "is resolved";
        if (overall)
          return test3(result);
        return catched("rejected on 2");
      }
    
      function test3(result) {
        // some process return in overall as boolean
        var overall = true;
        result.test3 = "is resolved";
        if (overall)
          return finished(result);
        return catched("rejected on 3");
      }
    
      function finished(result) {
        console.log(result);
      }
    
      function catched(result) {
        console.log(result);
      }
    
      test1();
    

    test1() will run first. After each of them done it goes to the next, syntax is simple, my homemade Promise.all is super simple thats the solution. Plus, if in future we want to add a middle function between test2 to test3, we should not think about adding it in the end handle.

    Hmm.. probably that is it for now. Hope it is clear to understand and help others!

    THANKS :)

    • zero298
      zero298 over 6 years
      Those code blocks are not equivalent. Promise.all() will execute all of the Promises that you give it at the same time, not sequentially.
    • Admin
      Admin over 6 years
      Correct me please, what should i use instead?
    • zero298
      zero298 over 6 years
      That being said, you may want to look into async functions. They give a more terse way of doing Promises.
    • guest271314
      guest271314 over 6 years
      What is the issue with code at Question?
    • Admin
      Admin over 6 years
      I have edited, and putted the issue: It is unreadable to concatenate too many functions manually one after one without forget something or redeclare something over again. It is vulnerable to syntax errors. (in real project this file could reach tons of lines). Therefore I need, or I shall define it as, we need, a one shot function or a method doing it for us without the chance of ruin the whole file.
  • guest271314
    guest271314 over 6 years
    What is the issue with code at Question? Note that await usage of code at Answer is outside of async function
  • guest271314
    guest271314 over 6 years
    What is the issue with code at Question?
  • zero298
    zero298 over 6 years
    @guest271314 I am answering under the presumption that the OP is trying to find a way to retain references to the results of the test* functions. I am assuming that is why the Promise.all() is appealing since you get a nice array of the results of all the Promises you give it. My answer shows a way to have your cake and eat it too, especially since, as I pointed out, the code blocks initially proposed are not equivalent.
  • guest271314
    guest271314 over 6 years
    The code at Question can pass the result of previous Promise value to next function which returns a Promise. OP simply does not return the value from the previous function which returns a Promise value to the next function which return a Promise
  • zero298
    zero298 over 6 years
    @guest271314 Then maybe I am misunderstanding the question. I read "after first promise is done, I can continue using it's resolved data in the next promise" as, "I want to keep the concrete value around, but I also want to feed it to the next function". To me, that necessitates breaking the Promise chain. If it is as you are interpreting it, then yes, you can just feed continuously as per Mark_M's answer.
  • guest271314
    guest271314 over 6 years
    Why would a "Promise chain" need to be broken?
  • zero298
    zero298 over 6 years
    @guest271314 Does my edit make sense?
  • guest271314
    guest271314 over 6 years
    OPs code is simply lacking passing the previous Promise value to next function which returns a Promise
  • Admin
    Admin over 6 years
    That is nice and cleaner for sure, but there is a problem in here: In console log I see: Test1 Finished Test2 Test3 while I have to see Test1, Test2, Test3 and Finish. But the resolved really pass between the promises functions.
  • Admin
    Admin over 6 years
    Nothing needs to be broken. I just want to kick out the "manually" way with then return then return then return that is very unreadable and syntax errors vulnerable! Promise.all does good work but not preserves the resolved data in this flow. Think about adding another cluster / function / promise in later situation, when using the "the manual way" you will forget add it to the handle promise for sure. No error will be displayed, just a user in db with missing details.
  • Admin
    Admin over 6 years
    Thanks. See my edit.