JavaScript execution in order

I am trying to print to a div one character at a time. It works, however, it runs both lines at the same time, so all I get is a jumbled mess.

How do I get the commands to run one by one?

function print(str){
    var arr = str.split("");
    var i = 0;
    function write(){
        setTimeout(function(){
            if(i < arr.length){
                var cont = $(".content").html();
                cont = cont.replace("_","");
                $(".content").html(cont + arr[i] + "_");
                i++;
                write();
            }
        },30);
    }
    write();
}

var str = [
    "I am the egg man",
    "I am the walrus"
];

for(x in str){
    print(str[x];
}

      

jsFiddle: http://jsfiddle.net/PscNC/1/

0


source to share


3 answers


You have two asynchronous functions that you run one after the other to execute in parallel. If you want them to run sequentially, then you need to create some kind of notification when the first one is done so that you can then start the next one and so on. This can be done in several ways (I show three ways below). You can use a callback, you can use promises, and you can avoid having to do async operations sequentially.

Method # 1 - Completion Callback

Here's adding a callback to your print function, then using that to trigger the next line to navigate, and then changing the iteration of the lines to use the callback:

Working demo: http://jsfiddle.net/jfriend00/Lyu5V/

$(function() {
    function print(str, fn) {
        var i = 0;
        var items = $(".content");

        function write() {
            setTimeout(function() {
                if (i < str.length) {
                    items.html(items.html().replace("_", "") + str.charAt(i) + "_");
                    i++;
                    write();
                } else {
                    fn();
                }
            }, 100);
        }
        write();
    }

    var data = [
            "I am the egg man...",
            "I am the walrus"
        ];

    var i = 0;
    function next() {
        if (i < data.length) {
            print(data[i++], next);
        }
    }
    next();
});

      

FYI, there is no reason to split your string into an array. You can access individual characters of a string using the method .charAt(index)

.


Method # 2 - promises - use .then () for sequence operations



And here's a version of your code using promises instead of passing a callback:

Working demo: http://jsfiddle.net/jfriend00/97UtX/

$(function() {
    function print(str) {
        var i = 0, items = $(".content"), def = $.Deferred();

        function write() {
            setTimeout(function() {
                if (i < str.length) {
                    items.html(items.html().replace("_", "") + str.charAt(i) + "_");
                    i++;
                    write();
                } else {
                    def.resolve();
                }
            }, 100);
        }
        write();
        return def.promise();
    }

    var data = [
            "I am the egg man..",
            "I am the walrus"
    ];

    data.reduce(function(p, item) {
        return p.then(function() {
            return print(item);
        });
    }, $.Deferred().resolve());

});

      


Method # 3 - Avoid consistency by combining data into one operation

And, if you want to simplify / dry it up a bit, you can do this, which avoids the sequential sequence of operations, by simply turning it into one longer operation, and I made a few simplifications for your code:

Working demo: http://jsfiddle.net/jfriend00/TL8pP/

$(function() {
    function print(str) {
        var i = 0, items = $(".content");

        function write() {
            setTimeout(function() {
                if (i < str.length) {
                    items.html(items.html().replace("_", "") + str.charAt(i) + "_");
                    i++;
                    write();
                }
            }, 100);
        }
        write();
    }

    var data = [
            "I am the egg man..",
            "I am the walrus"
    ];
    print(data.join(""));

});

      

+1


source


This is based on jfriend's answer, but it uses primitives with promises, not high level promises. I believe it makes for cleaner code.

First, write a function that represents delay with promises:

function delay(ms){ // generic delay function
     var d = $.Deferred();
     setTimeout(d.resolve, ms);
     return d;
}

      

Next, use promises to the fullest



var delay100 = delay.bind(null, 100); // a 100 ms delay

function write(el, str, initial) { // write a single word
    return [].reduce.call(str, function (prev, cur) { // reduce is generic
        return prev.then(delay100).then(function (letter) {
            initial += cur;
            el.text(initial + "_");
        });
    }, $.when());
}
data.reduce(function (p, item) {
    return p.then(function () { // when the last action is done, write the next
        return write($(".content"), item, ""); // might want to cache this
    });
}, $.ready.promise()); // we don't need `$(function(){})` 

      

Here is a fiddle illustrating this solution: http://jsfiddle.net/feq89/

Just for fun, here's an ES6 solution without jQuery:

var delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

var write = (el, str, initial) => 
    [].reduce.call(str, (prev, cur) =>
        prev.then(() => delay(100)).then(() => {
          initial += cur;
          el.textContent = initial + "_";
        });
    }, Promise.resolve());

var content = document.querySelector(".content");
data.reduce((p, item) => p.then(() => write(content, item, "")));

      

+1


source


bobef is right.

Add one more argument to print, which is a callback. And you have to call the print method inside another recursive method instead of a loop.

function print(str, _cb){
    var arr = str.split("");
    var i = 0;
    function write(){
        setTimeout(function(){
            if(i < arr.length){
                var cont = $(".content").html();
                cont = cont.replace("_","");
                $(".content").html(cont + arr[i] + "_");
                i++;
                write();
            } else {
                _cb();
            }
        },30);
    }
    write();
}

var str = [
    "I am the egg man",
    "I am the walrus"
];

var j = 0,
    callback = function () {
        if(j < str.length){
            print (str[j++], callback);
        }
    };

callback();

      

0


source







All Articles