How to wait for callback while iterating an array - javascript

I am working with a transnational framework within Javascript. So I need to wait for the previous query to finish before I move on. For example...
// Explicit this won't work because the length is not static
var i = [1,2,3]
doSomething(i[0], function(){
doSomething(i[1], function(){
doSomething(i[2], function(){
commitTransaction()
}
})
})
From this example I can't figure out a way to do this dynamically. It feels like a queue/recursion problem but I can't seem to crack it.
Does anyone else have an idea? I can also wrap in promises so that is an option as well, although that seems less synchronous.

Use async.eachSeries. So your code would translate to:
var transaction = {...};
async.eachSeries([1, 2, 3], function(value, callback) {
doSomething(value, transaction, callback);
}, function(err) {
if(err) throw err; // if there is any error in doSomething
commitTransaction(transaction);
});

jsFiddle Demo
I would suggest making a queue to do this. It would take the array, the generic callback function and a final function to callback with. Basically, the best way to accomplish this is to allow your functions to expect to have values injected.
The core assumption is that it is understood the caller will allow their callback function to have the current value and next callback function injected. That basically means we will end up with a function I have named queueAll which looks like this
function queueAll(arr,cbIteration,final){
var queue = [function(){ cbIteration(arr[arr.length-1],final) }];
for(var i = arr.length-2; i > 0; i--){
(function(next,i){
queue.unshift(function(){ cbIteration(arr[i],next) });
})(queue[0],i)
}
cbIteration(arr[0],queue[0]);
}
It takes the final call, places it in the queue, and then iterates, placing subsequent callback functions in the queue with the current value closed over, as well as closing over the front of the queue which at that point is the next call back. It is fairly simple to use. Pass it an array, a callback which expects values to be injected, and a final function.
In your case it would look like
queueAll(i,function(item,next){
doSomething(item,next);
},function(){
commitTransaction();
});
Stack Snippet Demo
//## <helper queue>
function queueAll(arr,cbIteration,final){
var queue = [function(){ cbIteration(arr[arr.length-1],final) }];
for(var i = arr.length-2; i > 0; i--){
(function(next,i){
queue.unshift(function(){ cbIteration(arr[i],next) });
})(queue[0],i)
}
cbIteration(arr[0],queue[0]);
}
//## </helper queue>
//## <user defined functions>
function doSomething(val,callback){
setTimeout(function(){
console.log(val);
callback();
},val*10);
}
function commitTransaction(){
console.log("commit");
}
//## </user defined functions>
//## <actual use>
var arr = [10,20,30];
queueAll(arr,function(item,next){
doSomething(item,next);
},function(){
commitTransaction();
});
//## </actual use>

Actually, I think promises are exactly what you're looking for. But for a traditional callback approach, consider the following:
var state = false,
doSomething = function (value, callback) {
/* do stuff with value */
if (!state)
doSomething(newValue, callback);
else
callback();
};

Related

Running Functions Synchronously in NodeJS (MongoDB Operations/Async.js)

I'm trying to do something fairly simple it seems in NodeJS - I want to run functions, one at a time. All of these functions have callbacks. I have outlined my code below, as well as the function that they run for further reference.
My problem is that the first two are working absolutely fine - one at a time, but the third iteration simply ignores the first two functions and just goes anyway. This is causing a real problem, since my program works with putting objects into a database, and it's causing duplicate objects.
The overall goal is to simply have each function run one at a time. Is there anything I'm missing here? Thanks so much for your help!
Please note that in the functions below, I have simplified all parameters to "args" for easier reading.
Calling the functions:
addNewProject(args);
addNewProject(args);
addNewProject(args);
Inside the functions, I run this:
function addNewProject(args) {
var info = args;
queue.push(function (done) {
loopThroughDetails(info, projID, 0, function () {
console.log('complete');
done(null, true);
});
});
}
This calls loopThroughDetails(), which is an integration to work with async.series():
function loopThroughDetails(info, projID, i, callback) {
if (i < 500) {
getProjectDetails(projID + "-" + i, function (finished) {
if (JSON.stringify(finished) == "[]") {
info.ProjID = projID + "-" + i;
DB_COLLECTION_NAME.insert(info, function (err, result) {
assert.equal(err, null);
callback();
});
} else {
i++;
loopThroughDetails(info, projID, i, callback);
}
});
}
}
And after calling all this, I simply use async.series to accomplish the task:
async.series(queue, function () {
console.log('all done');
});
What am I doing wrong here? Thanks so much for any help you can give! :)
Firstly, there are many methods to achieve what you are looking for and most are subjective. I like to use an array.shift method when iterating synchronously when possible. The concept goes something like this.
// say you have an array of projects you need to add.
var arrayOfProjects = [{name: "project1"}, {name: "project2"}, {name: "project3"}];
// This takes the first project off of the array and assigns it to "next" leaving the remaining items on the array.
var nextProject = function (array) {
// if there are items left then do work. Otherwise done.
if (array.length > 0) {
// shift the item off of the array and onto "next"
var next = array.shift();
addNewProject(next);
}
}
var addNewProject = function (project) {
// Do stuff with the project
console.log("project name: ", project.name);
// When complete start over
nextProject(arrayOfProjects);
}
// Start the process
nextProject(arrayOfProjects);
Here is a working Example
if you inspect the page you will see the projects logged to the console in order.

Nesting a Nested Function to Run in Sequence

I have three functions that I want to run in sequence (and then repeat, but I'm not even on that yet.) So when the first function displays its content and then leaves, the second function will play afterwards and do the same thing. Then that repeats into the third function. I'm using callbacks to try to achieve this.
This isn't a problem when I'm using only two functions, but when I introduce the third, It renders the first two menu boards, and then the third one comes afterwards, when they should render 1, 2 and then 3.
JavaScript for Reference
$(document).ready(function(){
Board1 = function(callback){
$('#menu-board .board.one .row').slideDown(800).delay(10000).slideUp(800, function(){
callback();
});
}
Board2 = function(callback){
$('#menu-board .board.two .row').slideDown(800).delay(10000).slideUp(800, function(){
callback();
});
}
Board3 = function(){
$('#menu-board .board.three .row').slideDown(800).delay(10000).slideUp(800);
}
Board1(Board2(Board3));
});
Any help is appreciated. Thank you.
Board1(Board2(Board3));
is equal to:
var res = Board2(Board3);
Board1(res);
So it won't act as you expect, it just start to execute Board2, and then start Board1, so Board3 is only guranteed to execute after Board2, while the order of Board1 is not relevant to Board2 and Board3.
You can use .bind to create a function that calls Board2 with give param Board3 like:
Board1(Board2.bind(null, Board3));
or just wrap them in another function:
Board1(function() {
Board2(Board3);
});
However, if you have too many functions to chain, use the methods above may not be a good idea, then you may create a chainer to do what you want:
// This function will accept a sequnce of functions in array, execute them in order, and call the done callback when all is complete.
var chain = function(sequences, done) {
// Manage the current index, and total items that would be called.
var idx = 0, length = sequences.length;
var caller = function() {
// When all functions in sequence is called, call final callback to notify user
// you may have to check if done is a function or not.
if (idx === length) {
if (typeof done === 'function') {
done();
}
return;
}
// Get the next function to call.
var currentTarget = sequences[idx];
// Pass caller to the target function, so when the function completes and call the callback
// the caller can takeover and start to call next function in sequence.
currentTarget(caller);
++idx;
};
caller();
};
// Create some test cases.
var sequence = [], i;
for (i = 0; i < 10; ++i) {
// Create some functions that will display some text after 1 sec when it get called.
sequence[i] = (function(index) {
return function(cb) {
setTimeout(function() {
var div = document.createElement('div');
div.innerHTML = 'Index is: ' + index;
document.body.appendChild(div);
cb();
}, 1000);
};
}(i));
}
// Demo.
chain(sequence, function() {
document.body.appendChild(document.createTextNode("All done."));
});
By the chain function above, you can now use it as chain([Board1, Board2, Board3]) and it keeps the codes simple even if you have a sequence of many functions.
PLUS:
From .slideUp()'s document:
Callback Function
If supplied, the callback is fired once the animation is complete.
This can be useful for stringing different animations together in
sequence. The callback is not sent any arguments, but this is set to
the DOM element being animated. If multiple elements are animated, it
is important to note that the callback is executed once per matched
element, not once for the animation as a whole.
As of jQuery 1.6, the .promise() method can be used in conjunction
with the deferred.done() method to execute a single callback for the
animation as a whole when all matching elements have completed their
animations ( See the example for .promise() ).
So if there's more than 1 element match to animate, the callback in your current function will get called more than once, you may have to rewrite your function with what the doc suggest to
Board1 = function(callback){
$('#menu-board .board.one .row').slideDown(800).delay(1000).slideUp(800).promise().done(callback);
}
You can see the jsfiddle that work as you expect.
why dont you just call the callback function directly in the slideup function.somewhat like this:
$('#menu-board .board.one .row').slideDown(800).delay(10000).slideUp(800, callback);
let me know if this does not work.
This is the reference for slideup function:
http://api.jquery.com/slideup/

Returning data from looped nested AJAX queries

I have a nested set of ajax calls, something like:
function getSubcategories(cat,callback) {
$.ajax({
url:'myurl.php',
data:'q='+cat,
dataType='json',
success:function(result){ callback(result) }
});
}
function getSubcatElements(subcat,callback) {
$.ajax({
url:'myurl2.php',
data:'q='+subcat,
dataType='json',
success:function(result){ callback(result) }
});
}
function organizeData(cat,callback) {
getSubcategories(cat,function(res){
totals=0;
list=new Array;
$.each(res['subcat'],function(key,val){
getSubcatElements(val,function(items){
$.each(items['collection'],function(key2,val2) {
list.push(val2['descriptor']);
});
totals+=items['count'];
// If I shove "totals" and "list" into an object here to callback, obviously gets called many times
}
// If I return an object here, it doesn't actually have counts from the asynchronous call above
}
function doStuff(cat) {
organizeData(cat,function() {
//stuff
});
So I'm running a looped asynchronous query that's a child of another asynch query, and I want the final result of the child loop without being "lazy". Right now I have it just returning updated results so the numbers change a few times, but I'd like to do it in one fell swoop.
It seems that the obvious place to do it would be to store the results in the asynch and return it after the $.each(), but JavaScript is insane and scoffs at things like obviousness. I feel like this should involve $.Deferred() but the samples I found all seemed like they should trigger after the first iteration ...
(The functions are deliberately separated as there is sometimes reason to use only one or only the other).
Thanks in advance!
Right now, your approach is fine. I want to add following changes in your code
function organizeData(cat, callback) {
getSubcategories(cat, function(res) {
totals = 0;
list = new Array();
totalSubCatItem = res['subcat'].length;
currentSubCatItem = 0;
$.each(res['subcat'], function(key, val) {
getSubcatElements(val, function(items) {
$.each(items['collection'], function(key2, val2) {
list.push(val2['descriptor']);
});
totals += items['count'];
// If I shove "totals" and "list" into an object here to callback, obviously gets called many times
// Here the solution
currentSubCatItem++;
if(currentSubCatItem === totalSubCatItem){
callback(/** pass argument here **/)
}
});
// If I return an object here, it doesn't actually have counts from the asynchronous call above
});
})
}
function doStuff(cat) {
organizeData(cat, function( result) {
//stuff
console.log(result)
});
}
First, you should probably organize your database query on the server side so it's returning a single, multi-plexed result. Rather than calling it lots of times.
Barring that, and assuming you don't know how many sub-categories you're going to call until your category call returns, your best bet is to create a global var that counts up every time it makes a call, and then counts down every time the callback receives a result. Whenever callback fires, counts down, and the new count is zero, do your updates.

How do I wrap executions of asynchronous (callback-based) functions into a synchronous function in Javascript?

I'm trying to write a function in Javascript (with jQuery, if you want):
function fetchItem(itemId) { return /* ??? */; }
This function relies on a second, predefined and unmodifyable function that looks like this:
function load(callback) { /* ... */ }
This function is asynchronous. After calling it, it fetches n items via XHR, then when they have arrived, stores them in the DOM, then invokes the callback.
fetchItem uses a simple jQuery selector (irrelevant here) to check the DOM for the element with itemId and calls load if the item isn't there yet. Rinse and repeat.
My problem is that I want to wrap multiple asynchronous calls of load into my synchronous fetchItem function, which should return the DOM element with itemId after it has made enough load calls.
Pseudo code, if load was synchronous:
function fetchItem(itemId):
while not dom.contains(itemId):
load()
return dom.find(itemId)
My first attempts at doing this in Javascript, which probably display a lot of misconceptions about Javascript's closures and execution model: ;)
function fetchItem(itemId) {
var match = undefined;
function finder() {
match = $(...).get(0);
if(!match) {
load(finder);
}
}
finder();
return match;
}
Obviously, this fails because the return is executed before the first callback. Also, as you can see I had some problems getting match back out to fetchItem. Is it properly protected by the closure here? Would this work if fetchItem was executed multiple times in parallel, assuming that load supports this (and doesn't mix up the DOM)?
I'm probably missing a perfectly good pattern here, but I don't really know what to google for...
You need to make fetchItems async too and provide it a callback, something like this should probably work (warning untested!):
function fetchItems(itemIDS, callback, matches) {
if (!matches) { // init the result list
matches = [];
}
// fetch until we got'em all
if (itemIDS.length > 0) {
var id = itemIDS[0]; // get the first id in the queue
var match = $(id).get(0);
// not found, call load again
if (!match) {
load(function() {
fetchItems(itemIDS, callback, matches);
});
// found, update results and call fetchItems again to get the next one
} else {
matches.push(match); // push the current match to the results
itemIDS.shift(); // remove the current id form the queue
fetchItems(itemIDS, callback, matches);
}
// we have all items, call the callback and supply the matches
} else {
callback(matches);
}
}
fetchItems(['#foo', '#bar', '#test'], function(matches) {
console.log(matches);
})
I would simply gave your fetchItem function as a callback to load. Like this:
function fetchItem(itemId, callback):
if not dom.contains(itemId):
load(fetchItem)
else:
callback(dom.find(itemId))
callback() is a function that does rest of the job when necessary element appears in the DOM.
That is impossible. You cannot create synchronousness from asynchronousness. Why do not you add a callback to your fetchItem-function as well?
Seems like everybody agrees that I need to introduce my own callback, so here's my (so far final) working solution:
var MAX_FETCH_MORE = 3;
/*
* Searches for itemId, loading more items up to MAX_FETCH_MORE times if necessary. When
* the item has been found or the maximum reload count has been reached, the callback
* is invoked, which is passed the DOM object of the item wrapped in a jQuery object, or
* undefined.
*/
function executeWithItem(itemId, callback, fetchCycleCounter) {
// initialize fetchCycleCounter on first iteration
if(!fetchCycleCounter) fetchCycleCounter = 0;
console.debug('iteration ' + fetchCycleCounter + '/' + MAX_FETCH_MORE);
// try to find the item in the DOM
match = $('div[data-item-id="' + itemId + '"]').get(0);
if(match) {
// if it has been found, invoke the callback, then terminate
console.debug('found: ' + match);
callback($(match));
} else if(!match && fetchCycleCounter < MAX_FETCH_MORE) {
// if it has not been found, but we may still reload, call load() and pass it
// this function as the callback
console.debug('fetching more...');
load(function() {executeWithItem(itemId, callback, fetchCycleCounter+1);});
} else {
// give up after MAX_FETCH_MORE attempts, maybe the item is gone
console.debug('giving up search');
}
}
// example invocation
executeWithItem('itemA01', function(item) {
// do stuff with it
item.fadeOut(10000);
});
Thanks to everybody for encouraging me to introduce another callback, it hasn't turned out looking so bad. :)

How do I store javascript functions in a queue for them to be executed eventually [duplicate]

This question already has answers here:
Semaphore-like queue in javascript?
(4 answers)
Closed 6 years ago.
I have created a Queue class in javascript and I would like to store functions as data in a queue. That way I can build up requests (function calls) and respond to them when I need to (actually executing the function).
Is there any way to store a function as data, somewhat similar to
.setTimeout("doSomething()", 1000);
except it would be
functionQueue.enqueue(doSomething());
Where it would store doSomething() as data so when I retrieve the data from the queue, the function would be executed.
I'm guessing I would have to have doSomething() in quotes -> "doSomething()" and some how make it call the function using a string, anyone know how that could be done?
All functions are actually variables, so it's actually pretty easy to store all your functions in array (by referencing them without the ()):
// Create your functions, in a variety of manners...
// (The second method is preferable, but I show the first for reference.)
function fun1() { alert("Message 1"); };
var fun2 = function() { alert("Message 2"); };
// Create an array and append your functions to them
var funqueue = [];
funqueue.push(fun1);
funqueue.push(fun2);
// Remove and execute the first function on the queue
(funqueue.shift())();
This becomes a bit more complex if you want to pass parameters to your functions, but once you've setup the framework for doing this once it becomes easy every time thereafter. Essentially what you're going to do is create a wrapper function which, when invoked, fires off a predefined function with a particular context and parameter set:
// Function wrapping code.
// fn - reference to function.
// context - what you want "this" to be.
// params - array of parameters to pass to function.
var wrapFunction = function(fn, context, params) {
return function() {
fn.apply(context, params);
};
}
Now that we've got a utility function for wrapping, let's see how it's used to create future invocations of functions:
// Create my function to be wrapped
var sayStuff = function(str) {
alert(str);
}
// Wrap the function. Make sure that the params are an array.
var fun1 = wrapFunction(sayStuff, this, ["Hello, world!"]);
var fun2 = wrapFunction(sayStuff, this, ["Goodbye, cruel world!"]);
// Create an array and append your functions to them
var funqueue = [];
funqueue.push(fun1);
funqueue.push(fun2);
// Remove and execute all items in the array
while (funqueue.length > 0) {
(funqueue.shift())();
}
This code could be improved by allowing the wrapper to either use an array or a series of arguments (but doing so would muddle up the example I'm trying to make).
Canonical answer posted here
Here is a nice Queue class you can use without the use of timeouts:
var Queue = (function(){
function Queue() {};
Queue.prototype.running = false;
Queue.prototype.queue = [];
Queue.prototype.add_function = function(callback) {
var _this = this;
//add callback to the queue
this.queue.push(function(){
var finished = callback();
if(typeof finished === "undefined" || finished) {
// if callback returns `false`, then you have to
// call `next` somewhere in the callback
_this.next();
}
});
if(!this.running) {
// if nothing is running, then start the engines!
this.next();
}
return this; // for chaining fun!
}
Queue.prototype.next = function(){
this.running = false;
//get the first element off the queue
var shift = this.queue.shift();
if(shift) {
this.running = true;
shift();
}
}
return Queue;
})();
It can be used like so:
var queue = new Queue;
queue.add_function(function(){
//start running something
});
queue.add_function(function(){
//start running something 2
});
queue.add_function(function(){
//start running something 3
});
Refer to the function you're storing without the () at the end. doSomething is a variable (that happens to be a function); doSomething() is an instruction to execute the function.
Later on, when you're using the queue, you'll want something like (functionQueue.pop())() -- that is, execute functionQueue.pop, and then execute the return value of that call to pop.
You can also use the .call() method of a function object.
function doSomething() {
alert('doSomething');
}
var funcs = new Array();
funcs['doSomething'] = doSomething;
funcs['doSomething'].call();
In addition, you can also add the function directly to the queue:
funcs['somethingElse'] = function() {
alert('somethingElse');
};
funcs['somethingElse'].call();

Categories

Resources