Search code examples
javascriptnode.jsasynchronousasync-awaites6-promise

Consecutive promises not resolving


I'm working on a project where I use some promises/timeout to delay the call of different functions.

the code of this project is a bit big, so I thought it might be better to make a sample of it and test it and find the reason that is causing the promise ( only last one ) to not resolve.

After making a sample of this code, the same bug that was happening to me occurred again, so it's when I knew that there's something wrong in my logic and wanted to seek some help:

I'll write the code below and explain it after:

let i = 0; // declaring i which help us recall the test method (until a given length)

//function 1 2 and 3 are 3 functions that take few seconds to resolve ( made it simpler for you guys 
// to understand my problem


function function1() {
  return new Promise((res, rej) => {
    setTimeout(() => {
      console.log("from function 1");
      res();
    }, 1000);
  });
}

function function2() {
  return new Promise((res, rej) => {
    setTimeout(() => {
      console.log("from function 2");
      res();
    }, 1000);
  });
}
//in function 3 if the i didn't reach the length given ( made static here=10) it will finish its code
// then call the test method with the new/incremented i
// this keep happening until i= the required length(10) where the function will return true
// and when the function 3 return back true.. the test function should resolve and call the final()
// function

function function3(i) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      console.log("from function 3");
      if (i + 1 == 10) {
        res({ bool: true });
      } else {
        i++;
        res({ bool: false, i: i });
      }
    }, 2000);
  });
}

function final() {
  console.log("final");
}

function test(i) {
  return new Promise((res, rej) => {
    function1().then((data) => {
      function2().then((data) => {
        function3(i).then((data) => {
          console.log("boolean: ", data.bool);
          if (data.bool) {
            console.log("true..so if");
             res();
          } else {
            console.log("false..so else");
            test(data.i);
          }
        });
      });
    });
  }); //end of promise
}

test(i).then(() => {
  final();
});

the code runs well and it prints few times consecutively:
from function 1
from function 2
from function 3
....

and here is where the final function should be called to print "final" but its not. while debugging, I noticed that the function 3 return back false 9 times and true in the last one and this true should cause the test function to resolve but its not.


Solution

  • Right now, in the case of the else, you have a dangling Promise not connected to anything else:

          } else {
            console.log("false..so else");
            test(data.i);
          }
    

    The currently running test function never has its res called after the recursive call, so it remains unresolved forever, so the initial call of

    test(i).then(() => {
      final();
    });
    

    never results in final running.

    While you could fix it by adding test(data.i).then(res), to ensure the Promises are all connected:

    let i = 0; // delaring i wich help us recall the test method (until a given length)
    
    //function 1 2 and 3 are 3 functions that take few seconds to resolve ( made it simpler for you guys 
    // to understand my problem
    
    
    function function1() {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 1");
          res();
        }, 100);
      });
    }
    
    function function2() {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 2");
          res();
        }, 100);
      });
    }
    //in function 3 if the i didnt reach the length given ( made static here=10) it will finish its code
    // then call the test method with the new/incremented i
    // this keep happening until i= the required length(10) where the function will return true
    // and when the function 3 return back true.. the test function should resolve and call the final()
    // function
    
    function function3(i) {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 3");
          if (i + 1 == 10) {
            res({ bool: true });
          } else {
            i++;
            res({ bool: false, i: i });
          }
        }, 200);
      });
    }
    
    function final() {
      console.log("final");
    }
    
    function test(i) {
      return new Promise((res, rej) => {
        function1().then((data) => {
          function2().then((data) => {
            function3(i).then((data) => {
              console.log("boolean: ", data.bool);
              if (data.bool) {
                console.log("true..so if");
                return res();
              } else {
                console.log("false..so else");
                test(data.i).then(res);
              }
            });
          });
        });
      }); //end of promise
    }
    
    test(i).then(() => {
      final();
    });

    It would be much better to avoid the explicit Promise construction antipattern, and avoid the Promise-as-callback antipattern. This can be done concisely by making test an async function:

    async function test(i) {
      await function1();
      await function2();
      const data = await function3(i);
      console.log("boolean: ", data.bool);
      if (data.bool) {
        console.log("true..so if");
        return;
      } else {
        console.log("false..so else");
        await test(data.i);
      }
    }
    

    let i = 0; // delaring i wich help us recall the test method (until a given length)
    
    //function 1 2 and 3 are 3 functions that take few seconds to resolve ( made it simpler for you guys 
    // to understand my problem
    
    
    function function1() {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 1");
          res();
        }, 100);
      });
    }
    
    function function2() {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 2");
          res();
        }, 100);
      });
    }
    //in function 3 if the i didnt reach the length given ( made static here=10) it will finish its code
    // then call the test method with the new/incremented i
    // this keep happening until i= the required length(10) where the function will return true
    // and when the function 3 return back true.. the test function should resolve and call the final()
    // function
    
    function function3(i) {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 3");
          if (i + 1 == 10) {
            res({ bool: true });
          } else {
            i++;
            res({ bool: false, i: i });
          }
        }, 200);
      });
    }
    
    function final() {
      console.log("final");
    }
    
    async function test(i) {
      await function1();
      await function2();
      const data = await function3(i);
      console.log("boolean: ", data.bool);
      if (data.bool) {
        console.log("true..so if");
        return;
      } else {
        console.log("false..so else");
        await test(data.i);
      }
    }
    test(i).then(() => {
      final();
    });

    Or, if you have to keep using .thens:

    function test(i) {
      return function1()
        .then(function2)
        .then(() => function3(i))
        .then((data) => {
          console.log("boolean: ", data.bool);
          if (data.bool) {
            console.log("true..so if");
            return;
          } else {
            console.log("false..so else");
            return test(data.i);
          }
        });
    }
    

    let i = 0; // delaring i wich help us recall the test method (until a given length)
    
    //function 1 2 and 3 are 3 functions that take few seconds to resolve ( made it simpler for you guys 
    // to understand my problem
    
    
    function function1() {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 1");
          res();
        }, 100);
      });
    }
    
    function function2() {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 2");
          res();
        }, 100);
      });
    }
    //in function 3 if the i didnt reach the length given ( made static here=10) it will finish its code
    // then call the test method with the new/incremented i
    // this keep happening until i= the required length(10) where the function will return true
    // and when the function 3 return back true.. the test function should resolve and call the final()
    // function
    
    function function3(i) {
      return new Promise((res, rej) => {
        setTimeout(() => {
          console.log("from function 3");
          if (i + 1 == 10) {
            res({ bool: true });
          } else {
            i++;
            res({ bool: false, i: i });
          }
        }, 200);
      });
    }
    
    function final() {
      console.log("final");
    }
    
    function test(i) {
      return function1()
        .then(function2)
        .then(() => function3(i))
        .then((data) => {
          console.log("boolean: ", data.bool);
          if (data.bool) {
            console.log("true..so if");
            return;
          } else {
            console.log("false..so else");
            return test(data.i);
          }
        });
    }
    test(i).then(() => {
      final();
    });

    Whenever you have a Promise, you should almost always return it, or be the initial asynchronous operation of the script.