I'm trying to create a function queue with several functions in it.
After the creation i want to execute each function in it's turn.
But these function have delayed instructions inside of them, so i want to wait for each of the functions to complete its execution before the continuing.
My attempts:
var funqueue = [];
funqueue.push( function() {fun1() });
funqueue.push( function() {fun2() });
funqueue.push( function() {fun3() });
executeFunctionQueue(funqueue);
Where the execute function is:
function executeFunctionQueue(funqueue){
var fun1=funqueue.pop;
$.when(fun1()).then(executeFunctionQueue(funqueue));
}
But this does not work.
How should i do it?
Try utilizing .queue() , .promise() ; see also Change easing functions on animations in jQuery queue
function fun1() {
return $.Deferred(function(dfd) {
setTimeout(function() {
dfd.resolve(1)
}, 1500)
}).promise().then(msg)
}
function fun2() {
return $.Deferred(function(dfd) {
setTimeout(function() {
dfd.resolve(2)
}, 1500)
}).promise().then(msg)
}
function fun3() {
return $.Deferred(function(dfd) {
setTimeout(function() {
dfd.resolve(3)
}, 1500)
}).promise().then(msg)
}
var funqueue = [];
funqueue.push(function() {
return fun1()
});
funqueue.push(function() {
return fun2()
});
funqueue.push(function() {
return fun3()
});
function msg(data) {
if (data === "complete") console.log(data)
else $("body").append(data + "<br>")
}
function executeFunctionQueue(funqueue) {
var deferred = funqueue.pop();
return deferred().then(function() {
// set `this` within `$.queue()` , `.then()` to empty object `{}`,
// or other object
return $({}).queue("fun", $.map(funqueue, function(fn) {
return function(next) {
// return `next` function in `"fun"` queue
return fn().then(next)
}
})).dequeue("fun").promise("fun")
.then(function() {
// return "complete" string when `fun` queue empty
return "complete"
})
});
}
executeFunctionQueue(funqueue)
.then(msg);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
Alternatively , using $.when()
function executeFunctionQueue(funqueue) {
return $.when(!!funqueue[funqueue.length - 1]
? funqueue.pop().call().then(function() {
return executeFunctionQueue(funqueue)})
: "complete")
}
executeFunctionQueue(funqueue)
.then(function(complete) {
console.log(complete)
});
function fun1() {
return $.Deferred(function(dfd) {
setTimeout(function() {
dfd.resolve(1)
}, 1500)
}).promise().then(msg)
}
function fun2() {
return $.Deferred(function(dfd) {
setTimeout(function() {
dfd.resolve(2)
}, 1500)
}).promise().then(msg)
}
function fun3() {
return $.Deferred(function(dfd) {
setTimeout(function() {
dfd.resolve(3)
}, 1500)
}).promise().then(msg)
}
var funqueue = [];
funqueue.push(function() {
return fun1()
});
funqueue.push(function() {
return fun2()
});
funqueue.push(function() {
return fun3()
});
function msg(data) {
if (data === "complete") console.log(data)
else $("body").append(data + "<br>")
}
function executeFunctionQueue(funqueue) {
return $.when(!!funqueue[funqueue.length - 1]
? funqueue.pop().call().then(function() {
return executeFunctionQueue(funqueue)})
: "complete")
}
executeFunctionQueue(funqueue)
.then(msg);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
If you have functions that return Promises, this can be done very simply with a function like sequence:
// sequence :: [(undefined -> Promise<undefined>)] -> Promise<undefined>
function sequence(fns) {
var fn = fns.shift();
return fn ? fn().then(sequence.bind(null, fns)) : Promise.resolve(undefined);
}
sequence assumes that your asynchronous/Promise-returning functions do not take any inputs and do not produce any outputs (that they are merely being called for side-effects.)
An example usage of the sequence function is:
sequence([f1, f2, f3]);
function f1() {
return new Promise(function (res) {
setTimeout(function () {
console.log('f1');
res();
}, 100);
});
}
function f2() {
return new Promise(function (res) {
setTimeout(function () {
console.log('f2');
res();
}, 1100);
});
}
function f3() {
return new Promise(function (res) {
setTimeout(function () {
console.log('f3');
res();
}, 10);
});
}
This will log out 'f1', 'f2', and 'f3' in order with the varying, specified time delays in between.
Use this
function executeFunctionQueue(funqueue){
if(!funqueue.length){
return
}
var fun1=funqueue.pop();
$.when(fun1()).then(function(){
executeFunctionQueue(funqueue)
});
}
Or even this if queued functions are not asynchronous.
function executeFunctionQueue(funqueue){
var fun=funqueue.pop();
fun()
if(!funqueue.length){
return
}
executeFunctionQueue(funqueue);
}
First create an array of functions as given:
var array_of_functions = [function1, function2, function3, function4];
When you want to execute a given function in the array try this:
array_of_functions[index]('mystring');
use deferred/promise pattern to execute functions on other function complete.
var createQueue = function () {
var d = $.Deferred(),
p = d.promise(),
triggerQueue = function () {
d.resolve();
};
return {
addToQueue: p.then,
triggerQueue: triggerQueue
}
};
var cq = createQueue();
cq.addToQueue(function () {
console.log("hi");
}).then(function () {
console.log("hello");
});
cq.triggerQueue();
In order to make a clean queue, your asynchronous functions will need to somehow signify when they are done, or the next function won't know when to begin. This means you cannot pass in just any old function; they'll need to follow some format. I'd suggest taking a done callback as the first parameter in your function calls. This way, you can support both synchronous and asynchronous functions.
var processQueue = function nextStep(queue) {
var next = queue.shift();
next && next(function() { nextStep(queue); });
}
function fun1(done) {
setTimeout(function() {
console.info('1');
done();
}, 1000);
}
function fun2(done) {
console.info('2');
done();
}
processQueue([fun1, fun2]);
// >> 1 second wait
// >> 1
// >> 2
Related
for example, if I need to do task s1,s2,s3 linerly,it would be look like this:
var s1=function(){
document.write("[1s task]");
setTimeout(s2,2000);
}
var s2=function(){
document.write("[2s task]");
setTimeout(s3,3000);
}
var s3=function(){
document.write("[3s task]");
}
setTimeout(s1,1000);
but it is very hard to maintain if I want to change the order from s1,s2,s3 to s3,s1,s2. How to wrap and 'objectize' a task which look like this:
mySetTimeout(new MyTask(s1,1000),new MyTask(s2,1000),new MyTask(s3,1000));
so that it is easy to change order from s1,s2,s3 to s3,s1,s2:
mySetTimeout(new MyTask(s3,3000),new MyTask(s1,1000),new MyTask(s2,2000));
? How to write mySetTimeout and MyTask?
The solution you didn't know you were looking for are promises. They're objects representing asynchronous results, and can be chained with callback functions.
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function s1() {
console.log("[1s task]");
return delay(1000);
}
function s2() {
console.log("[2s task]");
return delay(2000);
}
function s3() {
console.log("[3s task]");
return delay(3000);
}
s1().then(s2).then(s3);
Create a schedule task which takes different tasks as parameters (an example below)
function scheduleTask()
{
var args = [...arguments];
var firstTask = args.slice(0,1)[0];
args = args.slice(1);
//console.log(args, firstTask);
firstTask && setTimeout( function(){
firstTask( function(){
scheduleTask(...args);
});
}, 1000); //timeout is constant
}
scheduleTask(s1, s2, s3);
Demo
function scheduleTask()
{
var args = [...arguments];
var firstTask = args.slice(0,1)[0];
args = args.slice(1);
//console.log(args, firstTask);
firstTask && setTimeout( function(){
firstTask( function(){
scheduleTask(...args);
});
}, 1000); //timeout is constant
}
scheduleTask(s1, s2, s3);
function s1( cb )
{
console.log("s1");
cb();
}
function s2( cb )
{
console.log("s2");
cb();
}
function s3( cb )
{
console.log("s3");
cb();
}
Or as suggested by #Thomas you can shorten it by doing
function scheduleTask(firstTask, ...args)
{
firstTask && setTimeout(function() {
firstTask(function() {
scheduleTask(...args);
});
}, 1000); //timeout is constant
}
Demo
function scheduleTask(firstTask, ...args)
{
firstTask && setTimeout(function() {
firstTask(function() {
scheduleTask(...args);
});
}, 1000); //timeout is constant
}
scheduleTask(s1, s2, s3);
function s1(cb) {
console.log("s1");
cb();
}
function s2(cb) {
console.log("s2");
cb();
}
function s3(cb) {
console.log("s3");
cb();
}
I guess #Bergi's promise way is nice but you may still sequence your jobs without using promises. Just put your jobs in an array in the order you like them get processed and use a recursive sequencer.
var seqJobs = ([j,...js]) => j && setTimeout(_ => (j.job(), seqJobs(js)), j.dly),
jobs = [{job: _ => console.log("[0s task]"), dly: 500},
{job: _ => console.log("[1s task]"), dly: 2000},
{job: _ => console.log("[2s task]"), dly: 3000},
{job: _ => console.log("[3s task]"), dly: 1000}];
seqJobs(jobs);
I have a piece of code:
var a = false;
function wait(milliseconds, async) {
if(!async) {
setTimeout(function() {
console.log('Sync timer done.');
a = true;
return true;
}, milliseconds*1000);
}
(...)
f_recipe.forEach(function(item, index) {
if (obj['actual_step'] != 0 && obj['actual_step'] != index ) {
e = "Desync";
throw e;
};
console.log("Step: " + obj.actual_step);
if(item.substr(item.length - 6) != "false)"){
if (eval(item)) {
obj['actual_step']++;
}
} else {
eval(item);
var ival = setInterval(function(){
if(a) {
console.log('do the next thing');
clearInterval(ival);
}
}, 1000);
}
});
But when I get to 'do the next thing'(interval complete), the forEach loop doesn't continue to the next element of the array. 'a' is set to true after timeout (kind of a synchronous wait in JS). f_recipes is a string array with function call (e.g. 'wait(20, false)').
How to get it to work?
What you're trying to do seems like a very bad idea, but promises can help with this (using Bluebird here because it provides Promise.delay and Promise.each):
function wait(seconds, dontActuallyWait) {
return dontActuallyWait ? null : Promise.delay(seconds * 1000);
}
function runSequence(things) {
return Promise.each(things, function(thing) {
return eval(thing);
});
}
runSequence([
'console.log("hello")',
'wait(2, false)',
'console.log("hello again")',
'wait(5, false)',
'console.log("goodbye")'
]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.5.1/bluebird.min.js"></script>
I have a function as below:
function foo(args1, args2, retry)
{
if (retry <= 0)
return false;
var isDone = callAnotherFunction(args1, args2);
if(!isDone) {
setInterval(function () {
foo(args1, args2, retry-1);
},
2000);
}
else
return true;
}
So I am not sure if the above implementation is correct. But I need to use this function in another function. And use the above function in an if block to decide if the other statement needs to be executed. Below is the usage of the above function.
function useIt(args1, args2)
{
// Other code
let store = function() {
if(!foo(args1, args2, 5)) {
cleanStorage(args1, args2);
return;
}
}
So the problem is in function useIt(), cleanStorage() does not wait for foo() to be executed if I am using setInterval or setTimeOut. So how do I need to implement the function foo() ? Kindly help me.
consider using promises
foo can be rewritten like this (I've replace setInterval with setTimeout):
function foo(args1, args2, retry) {
return new Promise(function (resolve, reject) {
if (retry <= 0)
reject();
var isDone = callAnotherFunction(args1, args2);
if (!isDone) {
setTimeout(function () {
resolve(foo(args1, args2, retry - 1));
}, 2000);
}
else
resolve(true);
})
}
and then use it like this:
function useIt(args1, args2) {
// Other code
let store = function () {
foo(args1, args2, 5).then(function () {
cleanStorage(args1, args2);
});
}
}
You should use Promises to do this
Something like this:
function foo(args1, args2, retry)
{
return new Promise(function(resolve, reject) {
if (retry <= 0)
reject();
var isDone = callAnotherFunction(args1, args2);
if(!isDone) {
setInterval(function () {
retry = retry - 1;
isDone = callAnotherFunction(args1, args2);
if (isDone)
resolve();
},
2000);
}
else
resolve();
}
}
function useIt(args1, args2)
{
// Other code
let store = function() {
foo(args1, args2, 5).then(result => {
cleanStorage(args1, args2);
return;
}
}
}
I am load HTML (external app) into an iFrame
I want to "do" something (callback) when an element becomes available in my iFrame. Here how I wrote it, and I'd like to write this with Promises instead:
function doWhenAvailable(selector, callback) {
console.warn("doWhenAvailable", selector)
if ($('#myiFrame').contents().find(selector).length) {
var elt = $('#myiFrame').contents().find(selector);
console.info("doWhenAvailable Found", elt)
callback && callback(elt);
} else {
setTimeout(function() {
doWhenAvailable(selector, callback);
}, 1000);
}
}
Actually instead of using setTimeout, I'd like to use setInterval to repeat the "find element" until it's found and resolve the "promise".
No, you would not use setInterval, you just would wrap the timeout in a promise and drop the callback:
function wait(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t);
});
}
function whenAvailable(selector) {
var elt = $('#myiFrame').contents().find(selector);
if (elt.length)
return Promise.resolve(elt);
else
return wait(1000).then(function() {
return whenAvailable(selector);
});
}
Keeping your recursive style, it would have become something like that :
function doWhenAvailable(selector) {
var dfd = jQuery.Deferred();
console.warn("doWhenAvailable", selector)
if ($('#myiFrame').contents().find(selector).length) {
var elt = $('#myiFrame').contents().find(selector);
console.info("doWhenAvailable Found", elt)
return dfd.resolve(elt);
} else {
setTimeout(function() {
doWhenAvailable(selector).then(function(e) {
dfd.resolve(e);
});
}, config[env].wrapper.timeOutInMs);
}
return dfd.promise();
}
But I would have tried to avoid recursive calls here
The general idea is to return a promise instead of receiving a callback.
Example:
var xpto = function(res) {
return new Promise((resolve, reject) => {
if(res > 0) resolve('Is greater');
else reject(new Error('is lower'));
});
}
So in your case:
function doWhenAvailable(selector) {
function work(callback) {
if ($('#myiFrame').contents().find(selector).length) {
var elt = $('#myiFrame').contents().find(selector);
console.info("doWhenAvailable Found", elt)
callback(elt);
}
}
return new Promise((resolve, reject) => {
console.warn("doWhenAvailable", selector)
setInterval(() => work(resolve), 1000);
})
}
Here:
function doWhenAvailable(selector) {
return new Promise(function(resolve, reject){
console.warn("doWhenAvailable", selector)
if ($('#myiFrame').contents().find(selector).length) {
var elt = $('#myiFrame').contents().find(selector);
console.info("doWhenAvailable Found", elt)
resolve(elt);
} else {
setTimeout(function() {
doWhenAvailable(selector).then(function(data){
resolve(data);
});
}, config[env].wrapper.timeOutInMs);
}
}
}
And call your function like that:
doWhenAvailable("#elemId").then(function(elt){
//do what you want
});
Say I have an array of functions that invoke a setTimeout.
[
function(cb){
setTimeout(function(){
cb('one');
}, 200);
},
function(cb){
setTimeout(function(){
cb('two');
}, 100);
}
]
Is there a way to access the time parameter (200, 100) and save the sum of that to a variable?
I want to execute a function only when both of those functions are done
A better approach is to use promises and Promise.all:
var task1 = new Promise(function(resolve,reject) {
setTimeout(function() {
//do something
resolve();
}, 100);
});
var task2 = new Promise(function(resolve,reject) {
setTimeout(function() {
//do something
resolve();
}, 200);
});
Promise.all([task1, task2]).then(function() {
//will be executed when both complete
});
You can mimic it with a closure for the count.
function out(s) {
var node = document.createElement('div');
node.innerHTML = s + '<br>';
document.getElementById('out').appendChild(node);
}
var f = [
function (cb) { setTimeout(function () { cb('one'); }, 100); },
function (cb) { setTimeout(function () { cb('two'); }, 200); }
],
useCounter = function () {
var count = 2;
return function (s) {
count--;
out(s + ' ' + count);
!count && out('done');
}
}();
f[0](useCounter);
f[1](useCounter);
<div id="out"></div>