0

I have added a function to my project that outputs sentences with a timeout between the characters, which works fine. The problem is that JS executes all the function calls async, while I want it to wait for the previous sentence to complete before the next one starts.

I want this to be a chainable jQuery function that works with .delay eventually. I have quite a few sentences that I want to print so nesting callbacks would be tedious.

I have tried a number of methods, but the closest I've got is calling the function with a delay between each one, which gets pretty annoying when I have to time the function to finish.

Heres the most recent

var printMsg = function(msg) {
  var index = 0;
  var out = $('#out').append('<pre></pre>');
  var msgOut = setInterval(function() {
    out.children(':last-child').append(msg[index++]);
    if (index >= msg.length) {
      clearInterval(msgOut);
    };
  }, 150);
}

Then I have to call them like this

var timeout = 8000;
printMsg('Lorem ipsum Laboris Duis cupidatat ut id enim nisi');
setTimeout(function() {
  printMsg('Lorem ipsum Laboris Duis cupidatat ut id enim nisi');
}, timeout);
timeout += 8000;
setTimeout(function() {
  printMsg('Lorem ipsum Laboris Duis cupidatat ut id enim nisi');
}, timeout);

Fiddle

Intern
  • 5
  • 5

2 Answers2

2

Using Recursive function would be the best choice.You will have more control over the execution with this. As you can execute the next function only after completion of the current function using the code like below.

var msg = [
  '1 Lorem ipsum Laboris Duis cupidatat ut id enim nisi',
  '2 Lorem ipsum Laboris Duis cupidatat ut id enim nisi',
  '3 Lorem ipsum Laboris Duis cupidatat ut id enim nisi',
  '4 Lorem ipsum Laboris Duis cupidatat ut id enim nisi'
];

var printMsg = function(index, callback) {

  if (msg.length > index) { // if all messages are not yet loaded.
    var out = $('#out').append('<pre></pre>');
    var charIndex = 0;
    var interval = setInterval(function() {
      out.children(':last-child').append(msg[index][charIndex++]);
      if (charIndex >= msg[index].length) {
        clearInterval(interval);
        // resolve();
        printMsg(++index, callback); //increment the index and pass the same callback
        //Recursive function call.
      }
    }, 150);
  } else {
    callback(); // all the messages are loaded
  }

}

//trigger the chain reaction by passing the first message index
printMsg(0, function() {
  alert('all the messages are printed!! and Synchronously too');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="out">
  <div>

Note: The callback has no use in the above solution. Its used only to pass in a custom function while loading the messages, If the function printMsg is used in multiple places this wold be best approach. Else you might have to put the logic in the else part of the printMsg.

Rajshekar Reddy
  • 18,647
  • 3
  • 40
  • 59
2

Working example using native ES6 Promise and chaining: https://jsfiddle.net/5hro5zq1/

var printMsg = function(msg) {
  var promise = new Promise(function(resolve, reject){
    var index = 0;
    var out = $('#out').append('<pre></pre>');
    var msgOut = setInterval(function() {
      out.children(':last-child').append(msg[index++]);
      if (index >= msg.length) {
        clearInterval(msgOut);
        resolve();
      };
    }, 150);
  });
  return promise;
}

function printMessages(messages){
  if(messages.length){
    printMsg(messages[0])
      .then(function(){
        printMessages(messages.slice(1, messages.length))
      });
  }
}

var messages = [
  'This is the first sentence.',
  'This is another sentence.',
  'We can do this all day long...'
];

printMessages(messages);

If you go for something like this you probably want to use jQuery promises or at least use the polyfill: https://github.com/stefanpenner/es6-promise

dannyjolie
  • 10,959
  • 3
  • 33
  • 28