nested setTimeout functions when subfunctions include varying delays - javascript

I have a set of about 100 arguments that all take different amounts of time to run through a given function. Each is a brief animation on a page, animating a different part depending on the argument, and they take about 1-3 seconds each.
I checked this: Nested setTimeout alternative?
...but it only works when the subfunctions take the same amount of time,
I can collect the arguments in an array in the order they should go, i.e.:
args= [arg1, arg2, arg3, arg4...]
Currently my calls looks like this:
setTimeout(myfunction(arg1), 3000);
setTimeout(myfunction(arg2), 5000);
setTimeout(myfunction(arg3), 7500);
setTimeout(myfunction(arg4), 8500);...
I'd really like to be able to have code that says "when myfunction(arg1) is finished, wait 500 milliseconds and then execute myfunction(arg2), then when that is finished wait 500 ms and execute func3, etc."
I don't know how to incorporate that into either the running of the setTimeouts or the definition of myfunction().
Thank you.

Promises are a perfect way to chain async operations.
If you could consider changing the body of myFunction so that it returns a promise then you could chain those operation easily.
The body of myFunction would look like this
function myFunction(args, time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// here you do your stuff
resolve(); // resolve the promise when it's done
}, time);
})
}
And you call it this way
var args = [
{ args: "", timeout: 100 },
{ args: "", timeout: 300 }
]
var promise = Promise.resolve();
args.forEach(function (animation) {
promise = promise
.then(myFunction.bind(null, animation.args, animation.timeout))
// ^ chaining promise so that they fire one after another
})

You can just schedule your next task in the callback of the previous setTimeout, like that:
var tasks = [
{ arg : "arg1", delay : 3000},
{ arg : "arg2", delay: 2000},
{ arg : "arg3", delay : 2500}
];
function myFunction(arg) {
console.log(new Date(),arg);
}
function schedule() {
var task = tasks.shift();
if(task) {
setTimeout(function() {
myFunction(task.arg);
if(tasks.length) schedule();
},task.delay);
}
}
schedule();
This code will call myFunction("arg1") in 3000ms, then myFunction("arg2") in +2000ms and then myFunction("arg3") in +2500ms.
Each time it will remove (shift) the first element of your "task list",
and then stop once it is empty.
Take a note that this code will mutate your tasks array (by removing the next task from it on an each iteration), so you won't be able to reuse it.
If that is a problem, just use an explicit index to address the next task:
function schedule(tasks,i) {
if(i<tasks.length) {
setTimeout(function() {
myFunction(tasks[i].arg);
if(i+1<tasks.length) schedule(tasks,i+1);
},tasks[i].delay);
}
}
schedule(tasks,0);

I think the easiest way to sort out this problem is a promise chain:
var args = [1, 2, 3, 4, 5, 6]
var i = 0;
function doStuff(arg) {
//do stuff
console.log(arg)
}
function getPromise(cb, arg, time){
return function() {
return new Promise(function(resolve){
setTimeout(function(){
cb(arg)
resolve()
}, time)
})
}
}
var promise_chain = Promise.resolve();
while (args[i]) {
promise_chain = promise_chain.then(getPromise(doStuff, args[i++], 500))
}
This code will generate a bunch of promises. Each one waits 500ms to resolve itself and execute next bunch of code. Hope this help.

Related

Call function at predefined irregular intervals in JavaScript

I have to run some code after some predefined irregular intervals after users takes some action like a button click.
The intervals could be [1, 1.2, 2.05, 2.8, ...., 10.56], [0.2, 1.5, 3.9, 4.67, 6.8, .., 12.0] or something else. So, the function should be called after 1 second, 1.2 seconds, 2.05 seconds and so on after the initial button click.
How can I do that in JavaScript?
You use setTimeout inside a function that gets called by that setTimeout:
// intervals in seconds
const intervals = [1, 1.2, 2.05, 2.8, 10.56];
(function runIrregularly(runThisCode) {
if (intervals.length > 0) {
// get (and remove) the first element from the array:
const timeout = intervals.shift();
// and schedule the next call to run in the future
setTimeout(
() => runIrregularly(runThisCode),
timeout * 1000
);
}
// then run our real function
runThisCode();
})(doSomething);
function doSomething() {
console.log(Date.now());
}
Now, while we're using shift() to get elements from the start of the array, you should at that point go "hm, so it doesn't even care that it's an array?" and indeed: because this code doesn't iterate over anything, you can even replace that with a generating function that comes up with values as needed:
function getNextInterval() {
// do whatever you like here
return some number;
}
(function runIrregularly(runThisCode) {
if (intervals.length > 0) {
const timeout = getNextInterval();
...
(You could even get fancy by using a generator function if you wanted)
Also, we don't have to call the function "as we declare it" of course, we can also declare and use it separately:
function runIrregularly(runThisCode) {
if (intervals.length > 0) {
...
}
runThisCode();
}
// and then at some later point in the code
runIrregularly(doSomething);
And finally, if you need this to cycle, rather than run once, we can combine shift with push, and copy the interval list when we start:
function runIrregularly(runThisCode, intervals) {
// get and remove the first element from the array
const timeout = intervals.shift();
// then push it back on as the last element.
intervals.push(timeout);
setTimeout(
() => runIrregularly(runThisCode, intervals),
timeout * 1000
);
runThisCode();
}
// and then later:
runIrregularly(doSomething, intervals.slice());
With the interval.slice() being necessary so that we leave the original array pristine (otherwise using for other functions will make those not start at your first interval, but "wherever we are in the cycle" based on however many calls are running).
How to capture and stop the timeouts, I leave as an exercise to the reader (there are plenty of questions/posts about that on the web/SO to find).
You can also achieve this with async/await in combination with setTimeout:
setTimeout(function() {
}, 1000);
Would execute function after 1 second has passed.
You can achieve your goal by nesting setTimeout calls (see answer by Mike) or with promises (async/await):
function delayExecution(n) {
return new Promise((resolve) => {
setTimeout(resolve, n)
})
}
async function runIrregularly(fn, delays) {
for (const delay of delays) {
// suspend execution by `delay` amounts of seconds
await delayExecution(delay * 1000)
fn()
}
}
runIrregularly(() => {
console.log(Date.now(), "Hello")
}, [1, 2, 3])

How to create a queue of promises that will be sync when it's empty?

I have this problem in the jQuery Terminal library. I have an echo method that prints the stuff on the terminal and you can print a string, promise, or function that returns a promise or string (to simplify let's assume string or promise).
But the issue is that if you echo a few promises and strings they are not printed in order. The code was just waiting with the next echo until the promise was resolved. The problem is that it only works for one promise.
So I was thinking that I need a kind of data structure that will keep adding promises and it will wait for all promises. But I'm not sure how to do this.
The problem I have is that I can't just chain promises because the echo method needs to be synchronous when there is nothing in a queue and you print a string. But this is not how Promise A+ behaves they are always async (even Promise.resolve()). I have a lot of unit tests that rely on echo being synchronous and it will be break change and I don't want that.
My idea was to just create an array of promises but I'm not sure when I should remove the promise from the array so the queue can be empty when all promises are resolved and I can do synchronous call.
Something like:
class PromiseQueue {
constructor() {
this._promises = [];
}
add(promise) {
this._promises.push(promise);
}
empty() {
return !this._promises.length;
}
then(fn) {
if (this.empty()) {
fn();
} else {
Promise.all(this._promises).then(function() {
// what do do with this._promises?
fn();
});
}
}
}
I guess it's not that simple as in my attempt. I have no idea how to implement this behavior.
EDIT:
I have this two cases that I want to handle:
function render(text, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(text), delay);
});
}
term.echo(() => render('lorem', 1000));
term.echo('foo');
term.echo(() => render('ipsum', 1000));
term.echo('bar');
term.echo(() => render('dolor', 1000));
term.echo('baz');
setTimeout(function() {
// this should render immediately because all promises
// would be already resolved after 5 seconds
term.echo('lorem ipsum');
// so after the call I check the DOM and see the output
// this is important for unit tests and will be huge
// breaking change if echo don't render string synchronously
}, 5000);
NOTE: echo promise and function that return a promise in this example is the same the only difference is that function is re-invoked in each re-render (e.g. when browser or container is resized).
Another example is just:
term.echo('foo');
term.echo('bar');
term.echo('baz');
that should be also synced. I need a generic solution so you don't need to know exactly what echo is doing.
I would not even use Promise.all here - wait only for the first promise in the queue.
const term = {
/** an array while a promise is currently awaited, null when `echo` can be synchronous */
_queue: null,
echo(value) {
if (this._queue) {
this._queue.push(value);
} else {
this._echo(value);
}
},
/** returns a promise if the `value` is asynchronous, undefined otherwise */
_echo(value) {
try {
if (typeof value == "function") {
value = value();
}
if (typeof value.then == "function") {
this._queue ??= [];
return Promise.resolve(value).then(console.log, console.error).finally(() => {
while (this._queue.length) {
if (this._echo(this._queue.shift())) {
return;
}
}
this._queue = null;
});
} else {
console.log(value);
}
} catch(err) {
console.error(err);
}
}
};
function render(text, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(text), delay);
});
}
term.echo('foo');
term.echo(() => render('lorem', 1000));
term.echo('bar');
term.echo(() => render('ipsum', 1000));
term.echo('baz');
term.echo(() => render('dolor', 1000));
term.echo('quam');
setTimeout(function() {
// this should render immediately because all promises
// would be already resolved after 5 seconds
term.echo('lorem ipsum');
console.log('end');
}, 5000);
console.log('echos queued');
While I was editing the question I've realized that this is similar to exec behavior:
term.exec('timer --time 1000 "hello"');
term.exec('echo world');
term.exec('timer --time 1000 "hello"');
term.exec('echo world');
and I solve this using same mechanism that was proved to work.
I've added a flag:
if (is_promise(next)) {
echo_promise = true;
}
similar to paused flag.
Then when promise next is resolved. I used the same what was done in resume()
unpromise(next, function() {
echo_promise = false;
var original = echo_delay;
echo_delay = [];
for (var i = 0; i < original.length; ++i) {
self.echo.apply(self, original[i]);
}
});
unpromise is a function that I always use. It invokes the function as then callback or calls immediately. So it's sync by default but async when needed. You can find the code on GitHub jquery.terminal-src.js#L1072.
And then last thing in echo main code:
if (echo_promise) {
echo_delay.push([arg, options]);
} else {
echo(arg);
}
This is not very clean code because echo is invoked multiple times but it works. If you know a better solution please share.
Maybe this kind of code can be abstracted into single PromiseQueue interface.

Is it possible to have multiple thenable for same promise instance?

Will it work when i have multiple thenable functions for single promise object? I don't want nesting in this case. I want to perform different operation on the same promise object.
var a = 0,
t = null,
promiseFun = function () {
return new Ext.Promise(function (resolve, reject) {
t = setInterval(function () {
if (a == 1) {
resolve();
clearInterval(t);
} else if (a == 2) {
reject();
clearInterval(t);
}
}, 1000);
})
};
var promiseObj = promiseFun();
//handler one
promiseObj.then(function () {
console.log('resolved 1')
}, function () {
console.log('rejected 1')
}).then(function () {
console.log('resolved Nest')
}, function () {
console.log('rejected Nest')
});
//handler two- this is valid ?
promiseObj.then(function () {
console.log('resolved 2')
}, function () {
console.log('rejected 2')
});
//OUTPUT:
//resolved 1
//resolved 2
//resolved Nest
Or do i need to wrap in Deffered.
Will it work when i have multiple thenable functions for single promise object?
Yes, that works just fine. This is referred to as branching instead of chaining.
If you have this:
let p = somePromiseGeneratingFunction();
p.then(...).then(...);
p.then(...).then(...);
Then, you've just create two separate and independent promise chains that are each started when p resolves. If that's the logic you want, then that's perfectly OK.
The one thing you can guarantee is that the first p.then() handler will get called before the second p.then() handler gets called, but if there are further asynchronous operations in either chain, then they run independently after that and are no longer coordinated with each other. They are separate and independent promise chains that run according to the timing of their own asynchronous operations. That's why it's referred to as "branching". One chain branches into two chains.
See Is there a difference between promise.then.then vs promise.then; promise.then for a bit more discussion on the topic.

Chaining asynchronous function calls in JS?

So I'm fairly new to JavaScript and aware of asynchronous functions calls. I have done quite a bit of research and found that if you want to run asynchronous calls in succession of one another, you can use callback functions and promises. Now I have come to understand how both of these implementations are useful if you are running just a few asynchronous functions. I'm trying to tackle a completely different animal; at least to my knowledge. I'm currently building a site that needs to appear as if it's writing text to itself. Just to clue everyone here in to my JS code, here is the function that writes to the webpage (I'm fairly new so if you think you have a better solution, an example with a small description would be appreciated):
function write(pageText, elementId, delay) {
var element = document.getElementById(elementId);
var charCount = 0;
setInterval(function() {
if (charCount > pageText.length) {
return;
} else {
element.innerHTML = pageText.substr(0, charCount++);
}
}, delay);
}
write("This is an example", 'someRandomDiv', 100);
<div id="someRandomDiv">
</div>
With this I'm trying to write a line of text to a webpage one line after another. Essentially I'm use to writing code like such in Java and C#:
function writePassage()
{
var passage=["message one", "message two", "... message n"];
for(var i = 0; i<passage.length; i++)
{
write(passage[i], 'someRandomDiv', 100);
}
}
Obviously since this won't work because the for loop in wirtePassage() will finish executing before just one or two of the asynchronous function calls end. I'm asking if there is a sane solution to this error where I have n asynchronous calls, and I need to have one perform before the next one is triggered. It's worth mentioning that I don't want to just run this loop above and add another variable that just keeps track of how long I should delay each passage that will be written. I would prefer if there was a programmatic way that forces the execution of the function before the next one is called. Thanks for reading this monster question!
There are a few things you'll need to do to get this working.
First, your write function will need an asynchronous interface. As you mentioned it could either take a callback or return a promise. Taking a callback would look something like:
function write(pageText, elementId, delay, callback)
{
var element = document.getElementById(elementId);
var charCount=0;
var interval = setInterval(function(){
if(charCount>pageText.length)
{
clearInterval(interval);
callback();
}
else
{
element.innerHTML = pageText.substr(0,charCount++);
}
}, delay);
}
That calls callback when the full pageText has been written into element. Note that it also clears your interval timer when it's done, which avoids an event loop leak.
Then, you'll need to chain your asynchronous calls using this callback. You can do this quite cleanly with a library such as async:
function writePassage()
{
var passage=["message one", "message two", "... message n"];
async.series(passage.map(function(text){
return function(done){
write(text, 'someRandomDiv', 100, done);
};
}));
}
But it's also not so much trouble to do by hand:
function writePassage()
{
var passage=["message one", "message two", "... message n"];
var writeOne = function() {
if (!passage.length) return;
var text = passage.shift();
write(text, 'someRandomDiv', 100, writeOne);
}
// Kick off the chain.
writeOne();
}
That's just asynchronous recursion. Welcome to JavaScript. :)
A promise-based solution can also be pretty clean. First you need to return a promise from write:
function write(pageText, elementId, delay)
{
return new Promise(resolve) {
var element = document.getElementById(elementId);
var charCount=0;
var interval = setInterval(function(){
if(charCount>pageText.length)
{
clearInterval(interval);
resolve();
}
else
{
element.innerHTML = pageText.substr(0,charCount++);
}
}, delay);
}
}
Then you can create a chain of promises via reduction:
function writePassage()
{
var passage=["message one", "message two", "... message n"];
passage.reduce(function(chain, text) {
return chain.then(function(){
return write(text, 'someRandomDiv', 100, writeOne);
});
}, Promise.resolve());
}
In addition to Bo's answer, here is how you would do it with promises (because promises are awesome!). It is a bit more advanced, but I also find it more elegant (array method call on strings, reduce).
I also used arrow functions. If you need to support old browsers, you may want to replace them with regular functions.
// Return a promise resolved after time ms.
var wait = (time) => new Promise((resolve) => setTimeout(resolve, time));
function write(pageText, elementId, delay){
// Fetch the element.
var element = document.getElementById(elementId);
// Empty the element.
element.innerHTML = '';
// Reduce on each character of pageText with a resolved promise
// as a initialiser, and return the resulting promise.
return Array.prototype.reduce.call(pageText, (promise, char) => {
// Chain to the previous promise.
return promise
// First wait delay ms.
.then(() => wait(delay))
// Then add the current character to element's innerHTML.
.then(() => element.innerHTML += char);
}, Promise.resolve());
}
var messages = ["message one", "message two", "... message n"];
messages.reduce((promise, message) => {
return promise
// Write current message.
.then(() => write(message, "the-element", 100))
// Wait a bit after each messages.
.then(() => wait(400));
}, Promise.resolve());
<div id="the-element"></div>

Using generators to pause until promise resolves

I have a batch job in node.js that: copies files into a directory, does analysis on files, then removes files.
I would like to iterate over an array of jobs and use generators to pause execution until that batch job is complete before starting another job. Here is what I have so far:
const cars = ["toyota", "honda", "acura"];
function copyFilesAndRunAnalysis(car) {
return new Promise(function(resolve, reject) {
setTimeout(function() { // simulate some delay
resolve(); // control should return to generator here
}, 1000);
});
}
function* doCar(car) {
yield copyFilesAndRunAnalysis(car);
}
// BEGIN HERE
console.log('start here');
carBatch = doCar(cars[0]);
carBatch.next(); // confusion here!!!
carBatch.next(); // should this all be in a forEach loop?
What I'd like to do is have a forEach that loops over each car, does all the respective work in the copyFilesAndRunAnalysis method -- pausing until Promise.resolve() and then on to the next one. Trying forEach does not make anything run at all.
You do not use .value at js at Question. The .value of the next() object yielded by Generator would be the Promise returned from copyFilesAndRunAnalysis, where .then() could be chained to .next().value(), Array.prototype.shift() could be used to recursively call doCar until no items remain within original or copy of cars array.
const cars = ["toyota", "honda", "acura"];
let carsCopy = cars.slice(0);
function copyFilesAndRunAnalysis(car) {
return new Promise(function(resolve, reject) {
setTimeout(function() { // simulate some delay
resolve(car); // control should return to generator here
}, 1000);
})
}
function* doCar(cars) {
yield copyFilesAndRunAnalysis(cars);
}
// BEGIN HERE
console.log("start here");
carBatch = doCar(carsCopy.shift());
carBatch.next().value.then(function re(data) {
console.log(data);
return carsCopy.length
? doCar(carsCopy.shift()).next().value.then(re)
: "complete"
})
.then(function(complete) {
console.log(complete);
})
Note, the same process can be achieved utilizing Promise, recursion; without using a Generator function.
const cars = ["toyota", "honda", "acura"];
let carsCopy = cars.slice(0);
function copyFilesAndRunAnalysis(car) {
return new Promise(function(resolve, reject) {
setTimeout(function() { // simulate some delay
resolve(car); // control should return to generator here
}, 1000);
})
}
// BEGIN HERE
console.log("start here");
carBatch = copyFilesAndRunAnalysis(carsCopy.shift());
carBatch.then(function re(data) {
console.log(data);
return carsCopy.length
? copyFilesAndRunAnalysis(carsCopy.shift()).then(re)
: "complete"
})
// do stuff when all items within `cars` have been
// processed through `copyFilesAndRunAnalysis`
.then(function(complete) {
console.log(complete);
})
ES6 generators don't have anything to do with asynchronous execution. They provide usable mechanism for implementing async control flow in third-party code (particularly co).
It may be used like that
co(function* () {
console.log('start here');
for (let car of cars) {
yield copyFilesAndRunAnalysis(car);
}
console.log('end here');
});
co transforms wrapped generator function into a promise and doesn't make miracles. All asynchronous actions should be performed inside generator function.

Categories

Resources