0

I'm trying to execute an async function twice one after another. But the problem is that they are executing together. But, what I want is that the 2nd execution must be start after the execution of first is completed.

PS, there are many questions on SO, with same title, like this one JavaScript: execute async function one by one. But none of them solve my problem.

Please, check the Fiddle

$(document).ready(function() {
  function typeWriter(element, msg, speed, index = 0) {
    if (index < msg.length) {
      $(element).html($(element).html() + msg.charAt(index++));
      setTimeout(function() {
        typeWriter(element, msg, speed, index);
      }, speed);
    }
  }

  $('.intro').fadeIn(function() {
    const greet = new Promise(function(resolve, reject) {
      typeWriter('#sayHello', "Hello !!", 200);
      resolve();
    });
    greet.then(function() {
      typeWriter('#myName', "I'm learning programming", 200);
    });
  });
});
.intro {
  text-align: center;
  display: none;
  font-family: 'Amaranth', sans-serif;
}

.cursor {
  display: inline-block;
  width: 10px;
  height: inherit !important;
}

.cursor>span {
  display: inline-block;
  position: relative;
  top: 2px;
  width: 3px;
  height: 26px;
  margin: 0 0 0 0px;
  background-color: #000;
  animation: blink 1s step-end infinite;
}

@keyframes blink {
  from,
  to {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="intro">
  <h1 class="title"><span id="sayHello"></span><span class="cursor"><span></span></span>
  </h1>
  <h1 class="title"><span id="myName"></span><span class="cursor"><span></span></span>
  </h1>
</div>

Here, I'm trying to print two lines of text inside two different <h1> tags. For that, I'm calling function typeWriter(...) twice, once for each <h1>. I'm trying to print two lines, one after completion of previous one, i.e., print Hello !! in first call and after it finishes then print I'm Learning Programming in second line, but it is not happening. The problem is they are executing together.

On further searching for the solution, I found this question JavaScript: execute async function one by one, and follow answer given by @DavidHedlund, but It doesn't work for me. Sorry, if I'm making any mistake. Please take a look of the fiddle that I tried from @DavidHedlund answer. In this case, only Hello !! is printed.

$(document).ready(function() {
  function typeWriter(element, msg, speed, index = 0) {
    if (index < msg.length) {
      $(element).html($(element).html() + msg.charAt(index++));
      setTimeout(function() {
        typeWriter(element, msg, speed, index);
      }, speed);
    }
  }

  $('.intro').fadeIn(function() {

    function executeTasks() {
      var tasks = Array.prototype.concat.apply([], arguments);
      var task = tasks.shift();
      task(function() {
        if (tasks.length > 0)
          executeTasks.apply(this, tasks);
      });
    }

    function createTask(param) {
      let element = param[0];
      let msg = param[1];
      let speed = param[2];
      return function(callback) {
        setTimeout(function() {
          if (typeof callback == 'function') typeWriter(element, msg, speed);
        }, 1);
      }
    }
    var t1 = createTask(['#sayHello', "Hello !!", 200]);
    var t2 = createTask(['#myName', "I'm Anshuman Gupta", 200]);
    executeTasks(t1, t2);
  });
});
.intro {
  text-align: center;
  display: none;
  font-family: 'Amaranth', sans-serif;
}

.cursor {
  display: inline-block;
  width: 10px;
  height: inherit !important;
}

.cursor>span {
  display: inline-block;
  position: relative;
  top: 2px;
  width: 3px;
  height: 26px;
  margin: 0 0 0 0px;
  background-color: #000;
  animation: blink 1s step-end infinite;
}

@keyframes blink {
  from,
  to {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="intro">
  <h1 class="title"><span id="sayHello"></span><span class="cursor"><span></span></span>
  </h1>
  <h1 class="title"><span id="myName"></span><span class="cursor"><span></span></span>
  </h1>
</div>

Thank you!!

Anshuman
  • 758
  • 7
  • 23
  • In the first code-sample you have given, the "I am learning programming" starts after `typeWriter('#sayHello', "Hello !!", 200);` returns. So, if `typeWriter('#sayHello', "Hello !!", 200);` returns immediately (and does not wait for the text animation to complete) the next part will get executed. – zeekhuge Oct 07 '18 at 16:35
  • @ZeekHuge, please note that function `typeWriter` is a `recursive function`, which call itself untill the `length` of the message, `Hello !!` in this case. – Anshuman Oct 07 '18 at 16:37

3 Answers3

1

So, whats happening here is, the typeWriter() method is using a setTimeout(), which works on a threaded model (that is setTimeout() will not block the thread, instead will return immediately). That is why, the typeWriter() method returns immediately, and therefore the corresponding promise resolves.

Try this code

$(document).ready(function() {
    function typeWriter(element, msg, speed) {
        return new Promise (function (resolve, reject) {
            var recursive = function (element, msg, speed, index) {
                if (index < msg.length) {
                    $(element).html($(element).html() + msg.charAt(index++));
                    setTimeout(function() {
                        recursive(element, msg, speed, index);
                    }, speed)
                } else {
                    resolve();
                }
            }
            recursive(element, msg, speed, 0)
        })
        }

    $('.intro').fadeIn(function() {
        typeWriter('#sayHello', "Hello !!", 200).then(function() {
            return typeWriter('#myName', "I'm learning programming", 200);
        });
    });
})
zeekhuge
  • 1,594
  • 1
  • 13
  • 23
1

Alternatively, you could use await if that seems simpler for you.

$(document).ready(function() {
  function typeWriter(element, msg, speed, index = 0) {
    return new Promise((resolve, reject) => {
      if (index < msg.length) {
        $(element).html($(element).html() + msg.charAt(index++));
        setTimeout(function() {
          typeWriter(element, msg, speed, index).then(resolve).catch(reject);
        }, speed);
      } else {
        resolve();
      }
    });
  }
  
  async function typeStuff() {
    console.log("Hi");
    await typeWriter('#sayHello', "Hello !!", 200);
    await typeWriter('#myName', "I'm learning programming", 200);
  }

  $('.intro').fadeIn(function() {
    typeStuff();
  });
});
.intro {
  text-align: center;
  display: none;
  font-family: 'Amaranth', sans-serif;
}

.cursor {
  display: inline-block;
  width: 10px;
  height: inherit !important;
}

.cursor>span {
  display: inline-block;
  position: relative;
  top: 2px;
  width: 3px;
  height: 26px;
  margin: 0 0 0 0px;
  background-color: #000;
  animation: blink 1s step-end infinite;
}

@keyframes blink {
  from,
  to {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="intro">
  <h1 class="title"><span id="sayHello"></span><span class="cursor"><span></span></span>
  </h1>
  <h1 class="title"><span id="myName"></span><span class="cursor"><span></span></span>
  </h1>
</div>

Explanation: in your code, you called resolve immediately after calling typeWriter. The issue is that typeWriter does not "block," meaning it sets a timeout to execute code in the future and immediately returns. You can avoid this my making typeWriter a promise. Then, you can either use await (which is cleaner but less supported by browsers) or typeWriter(...).then(function() { /* Do more stuff */ }) (they are essentially equivalent).

soktinpk
  • 3,778
  • 2
  • 22
  • 33
1

The problem is you do not wait for the recursion function to finish and then call resolve.

 const greet = new Promise(function(resolve, reject) {
  typeWriter('#sayHello', "Hello !!", 200);
  resolve();
});
greet.then(function() {
  typeWriter('#myName', "I'm learning programming", 200);
});

Try this:

const timeout = (speed) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, speed);
  });
}

$(document).ready(function() {
async function typeWriter(element, msg, speed, index = 0) {
  if (index < msg.length) {
    $(element).html($(element).html() + msg.charAt(index++));
    await timeout(speed);
    await typeWriter(element, msg, speed, index);
  }
}

$('.intro').fadeIn(function() {
    const greet = new Promise(async function(resolve, reject) {
      await typeWriter('#sayHello', "Hello !!", 200);
      resolve();
    });
    greet.then(function() {
      typeWriter('#myName', "I'm learning programming", 200);
    });
  });
});
Anshuman
  • 758
  • 7
  • 23
Tarek Essam
  • 3,602
  • 2
  • 12
  • 21