I am using Jaydata as API for HTML5 indexedDB. I have a table in indexedDB where I need to query recursively. I need a callback when entire process is completed. Following is the recursive function. I need to have a callback when everything is done.
function getData(idValue) {
myDB.MySplDB
.filter( function(val) {
return val.ParentId == this.parentId;
}, {parentId: idvalue})
.toArray( function(vals) {
if(vals.length < 1) {
// some operation to store the value
} else {
for (var j=0;j<vals.length;j++) {
getData(vals[j].Id);
}
}
});
}
Adding .done(function(){...}); to .toArray doesn't work since it gets called before completion.
(Disclaimer: I work for JayData)
To wait for the finish of the entire process you need to use promises. You always have to return a promise. In the loop it gets tricky, return a super promise. So the code should be something like this:
function getData(idValue) {
return myDB.MySplDB
.filter( function(val) {
return val.ParentId == this.parentId;
}, {parentId: idvalue})
.toArray( function(vals) {
if(vals.length < 1) {
// some operation to store the value
// important: return a promise from here, like:
return myDB.saveChanges();
} else {
var promises = [];
for (var j=0;j<vals.length;j++) {
promises.push(getData(vals[j].Id));
}
return $.when.apply(this, promises);
}
});
}
getData(1)
.then(function() {
// this will run after everything is finished
});
remarks:
this example uses jQuery promises, so you'll need jQuery 1.8+
$.when uses varargs hence we need the apply
this can work with q promise with a slightly different syntax
Would this pseudo-code make sense in your case ?
var helper = function (accu) {
// Take an id from the accumulator
// query the db for new ids, and in the db query callback :
// If there is child, do "some operation to store the value" (I'm not sure what you're trying to do here
// Else add the child to the accumulator
// if accu is empty, call the callback, it means you reached the end
getData() would call this helper with an accumulator containing the first id, and your final callback
Related
Supposing we have a script that will execute a certain task for each row in an array.
function execute(err, array){
loop(array, function(err,object){
console.log(object)
//do a certain task when it's finished get into the next object successively.
});
}
function loop(array,callback){
array.forEach(function(object){
callback(null, object);
});
}
function array(callback){
callback(null, [1, 2, 3, 4, 5]);
}
setTimeout(function(){
array(execute);
}, 6000);
Questions:
How to get into the next loop only after finishing the task?
Is my function considered asynchronous ?
You can do something like this to iterate over your array :
var myarray = [1,2,3,4,5];
next(myarray, 0);
function next(array, idx) {
if (idx !== array.length) {
// do something
console.log(array[idx]);
// run other functions with callback
another_fct(array[idx], function() {
next(array, idx + 1); // then the next loop
});
// or run directly the next loop
next(array, idx + 1);
} else {
// the entire array has been proceed
console.log('an array of '+array.length+' number of elements have been proceed');
}
}
function another_fct(array_element, callback) {
// do something with array_element
console.log('value : '+array_element);
callback(); // run the next loop after processing
}
This method will perform your array elements synchronously.
Try this:
function execute(err, array) {
loop(array, function(err, object, next) {
console.log(object);
next(); // this will call recur inside loop function
}, function() {
console.log('All done');
});
}
function loop(array, callback, finish) {
var copy = array.slice();
(function recur() {
var item = copy.shift();
if (item) {
callback(null, item, recur);
} else {
if (typeof finish == 'function') {
finish();
}
}
})();
}
No, you function is not asynchronous but you call it asynchronously using setTimeout.
async provides async.series and other helpers for this purpose.
You can wrap your head around it better if you refactor the code and remove any redundant anonymous functions. The case is, that passing an anonymous function as an argument to another function not yet make the function asynchronous. You read more a more in depth explanation in "Does taking a callback make a function asynchronous?".
From the refactored version,
setTimeout(function() {
[1, 2, 3, 4, 5].forEach(function(object) {
console.log(object)
//do a certain task when it's...
});
}, 6000);
it can be seen that forEach is called for the array. The forEach method executes provided function once per array element and does it synchronously.
So, to answer your question:
yes, it calls the next item only after finishing previous (but if doing anything asynchronous, see below)
this is not considered asynchronous since it doesn't perform any asynchronous operations (not considering the outer setTimeout)
However, if you choose to start an asynchronous operation in the forEach function, then things change considerably. The result is that all operations are in-progress at the same time. This is potentially a hazardous operation resource-wise. There are libraries such as async for handling this use case gracefully.
(Btw, good use of Node.js errbacks, where first function argument reserved for passing potential error.)
This is my JS:
self.obj = {}
self.obj.accessErrors = function(data) {
var cerrorMessages = [];
for (prop in data) {
if (data.hasOwnProperty(prop)){
if (data[prop] != null && data[prop].constructor == Object) {
self.obj.fetch[accessErrors](data[prop]);
}
else {
cerrorMessages.push(data[prop]);
}
}
}
return cerrorMessages;
};
self.obj.fetch = {
X: function() {
// do stuff.
},
Y: function(callback) {
$http.get('/yposts/').then(
function(response) {
self.posts = response.data;
callback(self.posts);
},
function(response) {
self.posts = {};
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
callback(posts, cerrorMessages);
});
}
);
}
};
And I am getting an error pointing to this line:
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
The error says:
TypeError: self.obj.accessErrors(...).then is not a function
Any idea how to solve this?
self.obj.accessErrors(response.data) does not return a promise so therefore, you can't use promise methods on it.
If you want it to return a promise and you want that promise to reflect when all the fetch() operations are done and those operations are actually async, then you will have to make all your async code into using promises and you will have to combine them all using Promise.all() or the angular equivalent and convert from using callbacks in fetch to just using a promise. Right now, you have a mix which is difficult to program with.
The .then() construction is only needed when using Promise objects - essentially, instead of returning a value, the function returns an object that resolves to a value at some point in the future (which is then passed into the function that you pass to .then().
But you are right in that you need an asynchronous pattern to do this correctly, since fetch.Y is an asynchronous method. A good thing to do would be to create an array of promises at the beginning of your accessErrors function, like so:
var fetchPromises = [];
and then replace self.obj.fetch[accessErrors](data[prop]); with something that calls push on that array to add the promise that fetch returns to it.
Then, instead of returning accessErrors, return Promise.all(fetchPromises).
This will require some fairly significant modification to your code, however - namely, you will need to rewrite it so that it uses the Promise API instead of this callback by itself (which shouldn't be too difficult to do).
function(obj){
for (property in obj) {
if (obj.hasOwnProperty(property)) {
// some code here
if(condition){
obj.children = example.getdata(base, obj.name);
}
// some more code releated to obj
}
}
if (obj.length == 10)
{
//some more function
}
}
Now i want to make example.getdata async but i dont want to execute if statement that is after for loop until all the sync tasks are done.
Its more of like i want all the example.getdata function calls to execute in parallel and after they finish work i execute if (obj.length).
I tried using promises and push all the promises and resolve them but i dont know how to handle the return value for each function call.
You can use Promise s.
Think of something like:
var jobs = get_jobs(data);
when_all(jobs).done(function (jobs_result) {
console.log(jobs_result);
});
Where get_jobs returns a list or Promises and when_all will resolve when all it's input jobs are resolved.
To run async tasks serially in a loop, you can't use a regular for loop because the for loop won't wait for your async operation to complete before executing the next cycle of the loop. Rather, you have to do your own custom iteration a slightly different way. Here's one way:
Assuming your getdata() function is actually asynchronous and has a completion function, you could structure things like this:
function myFunc(obj, callback) {
var keys = Object.keys(obj);
var cntr = 0;
var results = [];
function next() {
if (cntr < keys.length) {
example.getdata(obj[keys[cntr++]], function(result) {
// do something with the result of each async operation here
// and put it in the results array
// kick off the next iteration
next();
});
} else {
// call the final callback because we are done now with all async operations
callback(results);
}
}
// start the first iteration
next();
}
If your getData function returns a promise or can be made to return a promise, then you can use promises for this too:
function myFunc(obj) {
var keys = Object.keys(obj);
keys.reduce(function(p, item) {
return p.then(function(result) {
// do something with the result of each async operation here
// and put it in the results array
return example.getdata(obj[item]);
});
}, Promise.resolve());
}
myFunc.then(function(results) {
// all async operations done here, use the results argument
});
Here is a clean example of a for loop using promises. If you can pick a promise library libraries like Bluebird will make it even simpler:
function(obj){
var props = Object.keys(obj), p = Promise.resolve();
props.forEach(function(prop){
p = p.then(function(){
if(condition){ // can access obj[prop] and obj here
obj.children = example.getData(...); // getData returns a promise
return obj.children; // return the promise.
}
});
});
Promise.resolve(obj.children).then(function(children){
// here the async call to get `.children` is done.
// can wait for `p` too (the loop) if we care about it
if(obj.length === 10) // do stuff
});
A Bluebird Promise returns an object that contains two arrays of objects, cars and contracts. Then, I want to iterate over the cars, call an asynchronous function and, based on the returned value, make some changes to the second array and return the initial result object with these changes. I can't figure out how to do this with promises. Or with async, for that matter. I feel like they should be nested promises, but i can;t get it to work at all.
the version with promises:
somePromise().then(function (result) {
Promise.each(result.cars, function (car) {
makeAsyncCall(car.id, function (err, resultArray) {
if (err) {
throw new Error();
}
result.contracts.forEach(function (contract) {
if (resultArray.indexOf(contract.id) > -1) {
contract.car = car.id;
}
});
});
}).then(function (eachResult) {
//eachResult is result.firstArray, which is not interesting.
return result;
});
}).then(function (result)) {
//this gets called before my promise.each gets executed??
}
Can anyone give me a hint as to where my mistake is?
Have a look at my rules of thumb for promise development. The two specific points that apply to your code are:
promisify your async callback-taking functions before using them, specifically
var makeCall = Promise.promisify(makeAsyncCall);
always return promises from your functions that do asynchronous things. This is especially true for callbacks, like the function() { Promise.each(…).then(…) } and the function() { makeAsyncCall(…) }.
With those, you should get to the following:
somePromise().then(function(result) {
return Promise.each(result.cars, function(car) {
return makeCall(car.id).then(function(resultArray) {
// a lookup structure of contracts by id could make this more efficient
result.contracts.forEach(function (contract) {
if (resultArray.indexOf(contract.id) > -1)
contract.car = car.id;
});
});
}).return(result);
}).…
I'm trying to work through this js/async scenario and i'm trying to know how the rest of the js world handles this.
function doStuff(callback) {
cursor.each(function(err, blahblah) {
...doing stuff here takes some time
});
... Execute this code ONLY after the `cursor.each` loop is finished
callback();
EDIT
Here's a more concrete example updated using most of the suggestions below which still doesn't work.
function doStuff(callback) {
MongoClient.connect(constants.mongoUrl, function(err, db) {
var collection = db.collection('cases2');
var cursor = collection.find();
var promises = []; // array for storing promises
cursor.each(function(err, item) {
console.log('inside each'); // NEVER GETS LOGGED UNLESS I COMMENT OUT THIS LINE: return Q.all(promises).then(callback(null, items));
var def = Q.defer(); // Create deferred object and store
promises.push(def.promise); // Its promise in the array
if(item == null) {
return def.resolve();
}
def.resolve(); // resolve the promise
});
console.log('items'); // ALWAYS GETS CALLED
console.log(items);
// IF I COMMENT THIS LINE OUT COMPLETELY,
// THE LOG STATEMENT INSIDE CURSOR.EACH ACTUALLY GETS LOGGED
return Q.all(promises).then(callback(null, items));
});
}
without using promises or any other dependencies/libraries you can simply
function doStuff(callback) {
add a counter
var cursor = new Array(); // init with some array data
var cursorTasks = cursor.length;
function cursorTaskComplete()
{
cursorTasks--;
if ( cursorTasks <= 0 ) {
// this gets get called after each task reported to be complete
callback();
}
}
for ( var i = 0; i < cursor.length; i++ ) {
...doing stuff here takes some time and does some async stuff
check after each async request
...when async operation is complete call
cursorTaskComplete()
}
}
Without knowing the details of the async calls you're making within the cursor.each loop, I shall assume that you have the ability to invoke a callback each time the functions invoked therein have completed their async task:
function doStuff() {
var promises = []; // array for storing promises
cursor.each(function(err, blahblah) {
var def = Q.defer(); // create deferred object and store
promises.push(def.promise); // its promise in the array
call_async_function(..., def.resolve); // resolve the promise in the async function's callback
});
// pass the array to Q.all, only when all are resolved will "callback" be called
return Q.all(promises);
}
and the usage then becomes:
doStuff().then(callback)
Note how the invocation of the callback now never touches the doStuff function - that function now also returns a promise. You can now register multiple callbacks, failure callbacks, etc, all without modifying doStuff. This is called "separation of concerns".
[NB: all the above based on the Q promises library - https://github.com/kriskowal/q]
EDIT further discussion and experimentation has determined that the .each call is itself async, and gives no indication to the outside when the last row has been seen. I've created a Gist that demonstrates a resolution to this problem.
if you want to do it with the async module, you can make use of the async forEachSeries function
Code snippet:
function doStuff(callback) {
async.forEachSeries(cursor, function(cursorSingleObj,callbackFromForEach){
//...do stuff which takes time
//this callback is to tell when everything gets over execute the next function
callbackFromForEach();
},function(){
//over here the execution of forEach gets over and then the main callback is called
callback();
});
}
In my mind an elegant/ideal solution would be to have something like
cursor.each(........).then( function() { ....your stuff});
But without that you can do this....UPDATED
http://plnkr.co/edit/27l7t5VLszBIW9eFW4Ip?p=preview
The gist of this is as shown below...notice....when
var doStuff = function(callback) {
cursor.forEach(function(cursorStep) {
var deferred = $q.defer();
var promise = deferred.promise;
allMyAsyncPromises.push(promise);
cursorStep.execFn(cursorStep.stepMeta);
promise.resolve;
});
$q.when(allMyAsyncPromises).then(callback);
}
After hitting the start button wait for few seconds...the async tasks have been simulated to finish in 5 seconds so the status will update accordingly.
Not having access to a real cursor object..I had to resort of fake cursor like and array.