Executing JavaScript in Order - javascript

I'm trying to print to a div one character at a time. It works, however, it runs both lines at the same time so that all I get is a jumbled mess.
How can I make the commands run one after the other?
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/

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(""));
});

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, "")));

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();

Related

Running Javascript Functions Sync

I am trying to make a javascript program that can take an array of words and output each word letter by letter in the screen and then delete each letter and go with the next word in the array. I have manage to make it work with one word of the array but when I use two or more they mesh with each other as if the were call async or in parallel. This is the code:
const words_js = document.querySelector('.words-js');
const words = [
'driven',
'condident',
'creative',
'inspired',
'productive',
'focused',
'fullfiling'
];
function runWords() {
words.forEach((word, index) => {
setTimeout(() => {
runLetter(word, 2, 0);
}, 1000 * (index))
});
}
function runLetter(word, max, count) {
count = count + 1;
if (count > max) {
return
};
[...word].forEach((letter, index) => {
setTimeout(() => {
if (count === 1) {
words_js.innerHTML += letter;
} else {
words_js.innerHTML = word.substring(0, word.length - index);
}
if (index === ([...word].length - 1)) {
runLetter(word, max, count)
}
}, index * 1000);
});
}
runWords();
<span class="words-js"></span>
Thank you all for your time!
As the comments all mentioned, using setTimeout is making your calls asynchronous. On top of that, you run into the problem of scoping by using setTimeout in a loop. This issue can be solved by scoping your setTimeout using IIFE.
(function (_i) {
setTimeout(() => {
/*** some code ***/
}, _i * delay)
}(i)
Another issue, is that your code has no way of letting you know it's done. So you can't process your queue of words.
This problem can be solved by taking another approach and some language features.
I think ( I may be mistaken ) you're trying to achieve a typed words effect. So I will name my functions accordingly.
typeWord(word, delay cb) Takes a word and generates a sequence of the strings: eg word = "abc" => ["a", "ab", "abc", "ab", "a", ""] and then applies the callback to each item in the array using the giving delay as an interval.
It's wrapped in a Promise, this way you can attach a callback to the .then() when the function is resolved.
The typedWords(words, delay, cb) is a recursive function that takes an array of words and passes it to the typeWord function one by one synchronously. It waits for the current word to finish before continuing.
Lastly you add the callback to actually change your div: (s) => words_js.innerHTML = s
const words_js = document.querySelector('.words-js');
const words = [
'driven',
'condident',
'creative',
'inspired',
'productive',
'focused',
'fullfiling'
];
function typeWord(word, delay, cb) {
let rangeOfWordLength = [...Array(word.length * 2)]
// Create an array of word segments
let seq = rangeOfWordLength.map((_, i) => (i >= word.length) ? word.slice(0, -(i + 1 - word.length)) : word.slice(0, i + 1));
// wrap each segment in a Promise which resolved after callback
let promises = seq.map((segment, i) => new Promise(_r => {
// Proper scoping
(function(_i) {
setTimeout(() => {
cb(segment, _r);
}, _i * delay)
}(i))
}));
// Return a promise that only fires after all the previous promises have resolved
return new Promise((resolve) => {
Promise.all(promises).then(resolve)
})
}
async function typedWords(words, delay, cb) {
if (words.length) {
let next = words.shift()
await typeWord(next, delay, cb);
return typedWords(words, delay, cb);
}
return true;
}
typedWords(words, 500, (s, next) => { words_js.innerHTML = s; next();})
<span class="words-js"></span>

loop 100+ getJSON calls and call a another function when completely done

I need to read a grid and take that data and call a $getJSON url. The grid could have over 100 lines of data. The getJSON returns a list of comma separated values that I add to an array. Once the loop is finished I take the array and process it for the duplicates. I need to use the duplicates in another process. I know that I can't determine the order of the data that is coming back but I need to know that all of the calls have been make.
for (let i = 0; i < rowscount; i++){
$.getJSON(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=" +
terms,
function (data) {
var pmids = data.esearchresult.idlist;
var pmidlist = pmids.join();
pmid_List.push(pmidlist);
if (i == rowscount - 1) {
// call the related function
}
});
}
I can't figure out how to be sure that the process has finished. The call to the related function has been done early at times.
Well if we keep track of how many have completed we can fire off the code when the last one is done.
let complete = 0;
for (let i = 0; i < rowscount; i++){
$.getJSON(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=" +
terms,
function (data) {
var pmids = data.esearchresult.idlist;
var pmidlist = pmids.join();
pmid_List.push(pmidlist);
complete += 1;
if (complete == rowscount) {
// call the related function
}
});
}
I'd use fetch and Promise.all
const link = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=";
Promise.all(Array.from({
length: 3
}, () => fetch(link + 'foo').then(e => e.json()))).then(e => {
//called when all requests are done
console.log(e);
})
Try this
function getJson(url, i) {
return $.getJSON(url, function (data) {
//var pmids = data.esearchresult.idlist;
//var pmidlist = pmids.join();
//pmid_List.push(pmidlist);
console.log('completed', i)
return data;
});
}
function run() {
let promises = []
for (let i = 0; i < rowscount; i++) {
const terms = 'foot';
const url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=500&term=" + terms;
promises.push(getJson(url, i));
}
return promises;
}
Promise.all(run()).then(() => console.log('All are completed'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Display letters one after the other for a looped sequence

I want to display multiple strings one after the other and I want the letters in each string to appear one at a time until the string is complete and it loops to the next string. Should run on a continuous loop.
var example = ['IT Solutions', 'Professional Work Ethic'];
textSequence(0);
function textSequence(i) {
if (example.length > i) {
setTimeout(function() {
document.getElementById("sequence").innerHTML = example[i];
textSequence(++i);
}, 4000);
} else if (example.length == i) { // Loop
textSequence(0);
}
}
<div class="container" id="sequence"></div>
I prefer #adpro's answer, but here's an alternative that keeps your original array:
showLettersOf(
['IT Solutions', 'Professional Work Ethic'],
document.querySelector('#sequence')
);
function showLettersOf(arrayOfStrings, el) {
var stringIndex=0, letterIndex=0, str="";
return setInterval(function(){
str += arrayOfStrings[stringIndex].charAt(letterIndex++);
el.innerHTML = str;
if (letterIndex >= arrayOfStrings[stringIndex].length){
letterIndex=0;
str="";
if (++stringIndex >= arrayOfStrings.length) stringIndex=0;
}
}, 100);
}
You can join the strings, and then loop over them almost the same as you are. But you'll want to hold on to the value and pass it in recursively to keep track of what you have already.
This may not be exactly what you want, but I think you'll be able to figure it out by using this.
var example = ['IT Solutions', 'Professional Work Ethic'];
var eString = example.join(' ');
textSequence(0, '');
function textSequence(i, value) {
if (eString.length > i) {
setTimeout(function() {
value += eString[i];
document.getElementById("sequence").innerHTML = value;
textSequence(++i, value);
}, 100);
} else if (eString.length == i) { // Loop
textSequence(0, '');
}
}
<div class="container" id="sequence"></div>

arrayname.forEach( function(element){ });

I have a successfully populated array that contain string elements.
And I recently learned about the forEach() of JavaScript
arrayname.forEach(
function(element){
// some statements
}
);
How can I make it work so the "some statements" only run once every three seconds? I tried setInterval and setTimeOut but it did not give me the result I desired. Thanks
I tried this as well
arrayname.forEach( function(element){ }).delay(3000);
but it still did not give me the result I wanted. :(
This function will do it - it's worth avoiding setInterval wherever possible - it has issues where it doesn't guarantee at least delay between calls, particularly if earlier calls have been queued up (perhaps because the window lost focus):
function forEachWithDelay(arr, callback, delay, thisArg) {
var index = 0, count = arr.length;
(function loop() {
if (index < count) {
callback.call(thisArg, arr[index], index, arr); // same as .forEach
++index;
setTimeout(loop, delay);
}
})(); // start the loop immediately
}
usage:
forEachWithDelay(arrayname, function(element, index) {
// do something
}), 3000);
NB: this will start the loop with zero delay for the first element.
var arr = ["Microsoft", "Google", "Apple"];
var i = 0;
function loop() {
alert( arr[i] );
i++;
if( i < arr.length ){
setTimeout( loop, 3000 );
}
};
loop();
This may be what you want:
var i=0;
var loop = setInterval(function(){
if(i < arrayname.length){
console.log(arrayname[i]);
i++;
}else{
clearInterval(loop);
}
},3000);
Here's how you can do it:
var index = 0;
var t = setInterval(function() {
console.log(arrayname[index++]);
if (index === arrayname.length) {
clearInterval(t);
}
}, 3000);
Try this:
$(document).ready(function () {
var array = ["first","second","third","fourth"];
var arr_length = array.length;
var index = 0;
callTime();
function callTime(){
setTimeout(function(){
index++;
if(index < arr_length){
callTime();
}
},1000);
}
});

throttle requests in Node.js

I have an array. I can loop over it with the foreach method.
data.forEach(function (result, i) {
url = data[i].url;
request(url);
});
The request function is making a http request to the given url.
However making all these requests at the same time leads to all sorts of problems.
So I thought I should slow things down by introducing some-sort of timer.
But I have no idea how will be able to combine a forach loop with setTimeOut/setInterval
Please note am doing this on the server (nodejs) rather on the browser.
Thanks for you help.
As your problem is global, you should adjust your request function to have only 5 request running at a time - using a global, static counter. If your request was before something like
function request(url, callback) {
ajax(url, callback);
}
now use something like
var count = 0;
var waiting = [];
function request(url, callback) {
if (count < 5) {
count++;
ajax(url, function() {
count--;
if (waiting.length)
request.apply(null, waiting.shift());
callback.apply(this, arguments);
});
} else
waiting.push(arguments);
}
data.forEach(function (result, i) {
url = data[i].url;
setTimeout(
function () {
request(url);
},
1000 * (i + 1) // where they will each progressively wait 1 sec more each
);
});
Instead of setTimeout could have them run in sequence. I assume there's a callback parameter to your request() function.
function makeRequest(arr, i) {
if (i < arr.length) {
request(arr[i].url, function() {
i++;
makeRequest(arr, i);
});
}
}
makeRequest(data, 0);
If you need a little more time between requests, then add the setTimeout to the callback.
function makeRequest(arr, i) {
if (i < arr.length) {
request(arr[i].url, function() {
i++;
setTimeout(makeRequest, 1000, arr, i);
});
}
}
makeRequest(data, 0);
you can delay call using setTimeout. following code will insure that each request get called after timerMultiPlier milliseconds from its previous request.
var timerMultiPlier = 1000;
data.forEach(function (result, i) {
setTimeout(function(){
url = data[i].url;
request(url);
}, timerMultiPlier*i );
});
Many of the above solutions, while practical for a few requests, unfourtunatly choke up and brick the page when dealing with tens of thousands of requests. Instead of queuing all of the timers at one, each timer should be qued sequentially one after another. If your goal is to have nice pretty fluffy code with lots of sugar and 'goodie-goodies' then below is the solution for you.
function miliseconds(x) { return x }
function onceEvery( msTime ){
return {
doForEach: function(arr, eachF){
var i = 0, Len = arr.length;
(function rekurse(){
if (i < Len) {
eachF( arr[i], i, arr );
setTimeout(rekurse, msTime);
++i;
}
})();
}
};
}
Nice, pretty, fluffy sugar-coated usage:
onceEvery(
miliseconds( 150 )
).doForEach(
["Lorem", "ipsum", "dolar", "un", "sit", "amet"],
function(value, index, array){
console.log( value, index );
}
)
function miliseconds(x) { return x }
function onceEvery( msTime ){
return {
doForEach: function(arr, eachF){
var i = 0, Len = arr.length;
(function rekurse(){
if (i < Len) {
eachF( arr[i], i, arr );
setTimeout(rekurse, msTime);
++i;
}
})();
}
};
}
You can the offset the execution delay of each item by the index, like this:
data.forEach(function (result, i) {
setTimeout(function() {
url = data[i].url;
request(url);
}, i * 100);
});
This will make each iteration execute about 100 milliseconds after the previous one. You can change 100 to whatever number you like to change the delay.
Some addings to this questions, just for knowledge base.
Created and async version without recursion.
function onceEvery(msTime) {
return {
doForEach: function (eachFunc, paramArray) {
var i = 0, Len = paramArray.length;
(function rekurse() {
if (i < Len) {
eachFunc(paramArray[i], i, paramArray);
setTimeout(rekurse, msTime);
++i;
}
})();
},
doForEachAsync: async function (eachFunc, paramArray, staticParamenters) {
var i = 0, Len = paramArray.length;
while (i < Len) {
await (async function rekurse() {
await eachFunc(paramArray[i], staticParamenters);
setTimeout(() => { }, msTime);
++i;
})();
}
}
};
}
module.exports = {
onceEvery
};
Put this code in a .js and call as simple as:
await throttle.onceEvery(100).doForEachAsync(loadJobFunction, arrayParam, { staticParam1, staticParam2, staticParam3 });
a very simple solution for quick throttling using async / await is to create a promise such as :
const wait = (delay) => new Promise((resolve, _) => {
setTimeout(() => resolve(true), delay)
})
Then await for it when you need to pause (in an async function):
//... loop process
await wait(100)
Note: you need to use a for loop for this to work, a forEach won't wait.

Categories