Running Javascript Functions Sync - javascript

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>

Related

I'm trying to print one letter after another

I'm trying to print one letter after another but this code just waits 100ms and then prints the value with no pauses. Any idea why this happens and how to fix it?
Code:
for (let i = 0; i < welcText.length; i++) {
setTimeout(()=>{
welcome.innerText += welcText[i];
},100);
}
Note that setTimeout does not actually delay the running of the code that comes after; it just schedules for something to happen in the future. So all the letters are added at the same time, after 100 milliseconds.
The most straightforward way to fix it is to make each subsequent timeout wait a bit longer, by multiplying the delay with the index of the letter (i * 100):
const welcome = document.getElementById('welcome');
const welcText = 'Welcome!';
for (let i = 0; i < welcText.length; i++) {
setTimeout(() => {
welcome.innerText += welcText[i];
}, i * 100);
}
<div id="welcome"></div>
The following function divtxt() returns a Promise. You can use it to send a txt to a div with a given id and let each letter appear in ms intervals. AND: you can build a chain of any number of follow-up actions with it:
function divtxt(id, txt, ms, wait = 0) {
const div = document.getElementById(id);
return new Promise((res, rej) => {
setTimeout(() => { // optional initial timeout, when wait>0
div.textContent = "";
const a = txt.split(""),
iv = setInterval(() => {
if (a.length)
div.textContent += a.shift();
else {
clearInterval(iv);
res(txt);
}
}, ms);
}, wait);
});
}
divtxt("welcome", "Hello world, this is my way of doing it! 🤩", 100)
.then(prevText => (console.log(prevText + ' is done.'),
divtxt("descr", "You can also chain this function with any number of consecutive actions."
+" Now: wait for 2 seconds ...", 100)))
.then(() => divtxt("welcome", "This promised-based approach REALLY lets you do it!! 👍🏻", 50, 2000))
.then(() => (console.log("ready?"),"Yes! I am REALLY DONE now! 😁"))
.then(console.log)
#welcome {
font-weight: 900
}
<div id="welcome"></div>
<div id="descr"></div>
You can do it using setInterval instead, and using an iterator to call the next char of the string every time. This is a bit more complicated, but has the advantage of not having multiple schedulers running if the string is too long. See the working example below.
const welcome = document.getElementById('welcome');
const welcomeText = 'Welcome!';
// returns a function that returns the next char in the string everytime its called
function createIterator(string) {
let currentIndex = 0;
return function() {
return string[currentIndex++];
}
}
// initializes the iterator with the text
let welcomeIterator = createIterator(welcomeText);
let welcomeInterval = setInterval(function() {
let nextChar = welcomeIterator();
// if we finish the string we clear the interval
if (!nextChar) {
return clearInterval(welcomeInterval);
}
// if the char exists, we append it to the div
welcome.innerText += nextChar;
}, 100);
<div id="welcome"></div>
you can do it like this also
const welcText = 'Welcome!';
let i = 0;
const interval = setInterval(() => {
if (i >= welcText.length) {
clearInterval(interval);
} else {
document.getElementById('helloword').innerHTML += welcText[i];
i++;
}
}, 100);
<h1 id="helloword"></h1>

Can a javascript async method return multiple values at different times?

I have a list of values that I wanna return asynchronously, but a limited number of values at a time.
So I call my function once and it has an array of say 20 distinct elements say [1,2,...,20], and it keeps on returning 5 elements every second. So I want:
[1,2,..,5] at 0 sec,
[6,7,..,10] at 1 sec,
and so on....
I was thinking on using buffer files. If it is a valid solution, can someone tell me how to implement it for the given example?
An async generator function (or method) can yield (loosely, return) a sequence of values over time. Here's an example:
function sleepRandom(maxMs) {
return new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * maxMs)));
}
async function *example(max) {
yield -1; // Example of explicit value
// Wait 500ms
await sleepRandom(800);
// Example of loop
for (let i = 0; i < max; ++i) {
// Wait 500ms
await sleepRandom(800);
// Yield current loop value
yield i;
}
// All done
yield "Done";
}
(async () => {
for await (let value of example(10)) {
console.log(value);
}
})()
.catch(error => {
console.error(error);
});
The * after function is what makes it a generator function. The async before function is what makes it async.
You could use nodes EventEmitter for that:
var events = require('events');
function spitChunks(arr, chunksize = 5, delay = 1000, em = null) {
if(!em) em = new events.EventEmitter();
if(arr.length > 0) setTimeout(() => {
em.emit('nextChunk', arr.splice(0,chunksize));
spitChunks(arr, chunksize, delay, em);
}, delay);
return em;
}
let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
let emitter = spitChunks(numbers);
emitter.on('nextChunk', data => console.log(data));

How to pause a loop after x iterations for x seconds then resume

So I have an array that has a list of Channel Names that I would like to join (This is for a Twitch Chat Bot), the API endpoint for joining a channel has a rate limit of 50 joins per 15 seconds. I am trying to figure out the best way to iterate through 50 channel names, pause for 15 seconds, then resume iterating through the rest of the array, pausing for 15 seconds every 50 names.
I had originally tried a generic for loop using a fake, 100 value array, a Modulus Operator, and setTimeout, but to no avail. But in all honesty, I didn't know where to start so it is quite bad.
let array = ([...Array(100).keys()].map(x => ++x))
for (var i = 1; i < array.length; i++) {
if (i % 50 === 0) {
setTimeout(() => {
console.log('Waiting 15 seconds.')
}, 15000);
} else {
console.log(array[i])
}
}
Ideally, it would log 1-50, then wait 15 seconds, then log 51-100.
You can use async and await to pause your for loop in a fairly simple fashion:
// utility function that returns a promise that resolves after t milliseconds
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
async function processArray(array) {
for (let i = 1; i < array.length; i++) {
if (i % 50 === 0) {
await delay(15 * 1000);
}
console.log(array[i])
}
}
let data = ([...Array(100).keys()].map(x => ++x))
processArray(data).then(() => {
console.log("all done");
});
FYI, I don't quite understand why you're trying to use index 1 through 100 on a 100 element array. I think you should be using indexes 0 through 99 for a 100 element array. I left the code that way you had in under the assumption that maybe you're doing this on purpose.
const _ = require('lodash');
Use iterators/generators so that you can control when you want the next item instead of fighting to "stop" the execution of the loop.
function* myIterator(data) {
for (const item of data)
yield item;
}
Then set up a function that will do the actual execution, taking the iterator as a parameter. This way, each time you call it, you can pass in the iterator so that it remembers where it left off.
function execute(it) {
// We want to do this in batches of 50
// (but test with lower value to better see how it works)
_.range(0, 50).forEach(() => {
// Get next item
const item = it.next();
// If no more items, we're done
if (item.done) return;
else {
// Do something with the item
console.log(item.value);
};
});
// Pause for 15 seconds and then continue execution
setTimeout(() => execute(it), 15000);
}
Create your data, generate an iterator from it and then execute.
(function main() {
const data = _.range(1, 101);
const it = myIterator(data);
execute(it);
})();
Try this
let array = ([...Array(100).keys()].map(x => ++x))
const startLoop = currentIndex => {
for (let i = currentIndex; i < array.length; i++) {
if (i % 50 === 0) {
setTimeout(() => {
console.log('Waiting 15 seconds.');
startLoop(i + 1);
}, 15000)
break;
}
console.log(array[i])
}
}
startLoop(1);
Writing a recursive loop function from scratch (at the cost of performance) is probably the simplest solution, but you can accomplish this iteratively using a while loop and a Promise, without compromising performance.
In the code below, every time the 1-based index of the loop reaches a multiple of batch_size, an await is called which stops execution until the Promise resolves. The promise is just a setTimeout call, which waits for pause_ms before allowing the loop to continue. The values are slightly different here just to make testing easier; you can change them freely to meet your needs.
const vals = [...new Array(20)].map(() => Math.floor(Math.random() * 9) + 1);
console.log(vals);
async function iterateBatches(arr, batch_size, pause_ms) {
// Create a promise generator that will automatically resolve after the desired number of millseconds.
const wait = () => new Promise(r => {
setTimeout(r, pause_ms)
});
// Establish the starting and ending points for the iteration.
const len = arr.length;
let i = 0;
// As long as the loop hasn't reached the final element,
while (i < len) {
// Perform an operation with the value in your array.
console.log(i, vals[i]);
// If we've reached the end of the array, break out of the loop to prevent an unneeded iteration.
if (i >= len - 1) break;
// Increment the index (this is also equal to the 1-based index, which saves us some lines).
// If the 1-based index is a multiple of batch_size and we're not on the first iteration, wait for our promise generator.
if (++i % batch_size === 0 && i > 0) await wait();
}
console.log("Done!");
}
iterateBatches(vals, 5, 2000);
You could just create the loop manually. Here's a simplified example...
var array = ['a','b','c','d','e','f'];
var i = 0;
function loop(){
if(i>=array.length) return;
if(i==3){
setTimeout(function(){
i++;
loop();
},15000);
return;
}
i++;
console.log(array[i]);
loop();
}
loop();

Executing JavaScript in Order

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

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