Executing JavaScript In Order
Solution 1:
You have two asynchronous functions that you start one right after the other so they run in parallel. If you want them to run serially, then you have to create some sort of notification when the first one is done so you then can trigger the start of the next one and so on. This can be done a number of ways (I show three ways below). You can use a callback, you can use promises and you can avoid having to sequence the async operations at all.
Method #1 - Completion Callback
Here's adding a callback to your print function and then use that to trigger the next string to go and then changing your iteration of strings 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's really no reason to split your string into an array. You can access the individual characters of the string with the .charAt(index)
method.
Method #2 - Promises - use .then() to sequence operations
And, here's a version of your code using promises instead of passing the 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 sequencing by combining data into one single operation
And, if you want to simplify/DRY it up a bit, you can do this which avoids having to sequence the successive operations by just turning it into one longer operation and I made a few simplifications to 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(""));
});
Solution 2:
This is based on jfriend's answer, but it uses primitives with promises rather than promises at a high level. I believe this makes for cleaner code.
First, let's write a function that represents a delay with promises:
function delay(ms){ // generic delay function
var d = $.Deferred();
setTimeout(d.resolve, ms);
return d;
}
Next, let's use promises to their 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 is 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, "")));
Solution 3:
bobef is right.
Add another argument to print, which is a callback. And you should call the print method inside another recursive method instead 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();
Post a Comment for "Executing JavaScript In Order"