I am building a native desktop application in javascript using CEF, And I have API to access filesystem from CEF. I have a senario,
in which I need to get names of all files(there could be trees of directories) within a particular directory. I need to get result array, I am using jquery promises. What I don't understand is: when do I resolve the promise to get final result array?
/*read all directories under this and get path*/
var result = [];
function _processEntries(dirPath) {
var dirEntry = new NativeFileSystem.DirectoryEntry(dirPath), deferred = new $.Deferred();
/*async call*/
dirEntry.createReader().readEntries(
function (entries) {
for (var i = 0; i < entries.length; i++) {
if (entries[i].isDirectory) {
_processEntries(entries[i].fullPath).done(function () {
deferred.resolve(result);
});
} else {
result.push(entries[i].fullPath);
}
}
},
function (error) {
console.log("Failed while reading dir:", error);
}
);
return deferred.promise();
}
// Calling function
_processEntries("C:/Users/abc").done(function(result){
console.log("FILES ARRAY:",result);
});
Please suggest any other technique if I am doing it wrong :)
Mmmm i see few errors you resolve the entire promise when the first child returns, you must wait for all your child directory finish there respective promise, and your are resolve a global variable not internal, look this example with the changes:
function _processEntries(dirPath) {
var result = [];
var dirEntry = new NativeFileSystem.DirectoryEntry(dirPath), deferred = new $.Deferred();
/*async call*/
dirEntry.createReader().readEntries(
function (entries) {
var promises = [];
for (var i = 0; i < entries.length; i++) {
if (entries[i].isDirectory) {
promises.push(_processEntries(entries[i].fullPath));
} else {
result.push(entries[i].fullPath);
}
}
if(promises.length === 0) {
deferred.resolve(result);
} else {
$.when.apply($,promises).done(function() {
result.concat(arguments);
deferred.resolve(result);
})
}
},
function (error) {
console.log("Failed while reading dir:", error);
}
);
return deferred.promise();
}
when do I resolve the promise
Immediately when the asynchronous call has ended. You would have the deferred antipattern otherwise. Avoiding that, you can work with promises everywhere, and use proper composition with them.
Also, you should not initialise your result variable outside of the _processEntries function - you'd get huge problems with calling the function multiple times.
function getDirectoryEntries(dirPath) {
// a promise helper function
var dirEntry = new NativeFileSystem.DirectoryEntry(dirPath),
deferred = new $.Deferred();
dirEntry.createReader().readEntries(deferred.resolve, deferred.reject);
return deferred.promise();
}
function _processEntries(dirPath) {
return getDirectoryEntries(dirPath).then(function (entries) {
var promises = [];
for (var i = 0; i < entries.length; i++) {
if (entries[i].isDirectory) {
promises.push(_processEntries(entries[i].fullPath));
} else {
promises.push($.when(entries[i].fullPath));
}
}
return $.when.apply($, promises).then(function() {
return [].concat.apply([], arguments);
});
});
}
Call it like this:
_processEntries("C:/Users/abc").then(function(result){
console.log("FILES ARRAY:",result);
}, function (error) {
console.log("Failed while reading dir:", error);
});
Related
I have a function which gives me response in sequence for asynchronous call using promises for a for loop but loop breaks when i got a exception from code, but i want to continue my loop even after a exception throw from function.
my async function is
function asyncFun(a) {
var q = $q.defer();
setTimeout(function(){
if(a == 4) throw new Error('custom error');
q.resolve(a);
}, 1000);
return q.promise;
}
and chain function is
function getData() {
var chain = $q.when();
for (var i = 0; i < 10; i++) {
(function(i) {
chain = chain.then(function() {
return asyncFun(i).then(function(res) {
console.log(res);
}).catch(function(ex) {
throw ex;
});
}).catch(function(ex) { throw ex });
})(i);
};
return chain
}
and when i call getData(); it stop the loop after throw error on i = 4 but i want to continue the for loop for all 10 entry.
Any help will be appreciated.
As I said in comment, the errors may be treated as special values so you can perform special behaviours after the chain promises.
Try this code:
function getData() {
var chain = $q.when();
for (var i = 0; i < 10; i++) {
(function(i) {
chain = chain.then(function() {
return asyncFun(i).then(function(res) {
console.log(res)
}).catch(function(ex) {
console.log(ex); // do not throw error but handle the error
});
}).catch(function(ex) { throw ex });
})(i);
};
return chain
}
I have a asynchronous function that needs to be called multiple times in the correct order. It's about uploading images to a server but like I said the images should be uploaded in the correct order.
My function looks like this:
function uploadImage(sku,fileName) {
console.log("Uploading image for sku="+sku+", imageName="+fileName);
var deferred = Q.defer();
var readStream = fs.createReadStream(rootPath+'/data/'+sku+"/"+fileName);
var req = request.post('http://localhost:3000/'+sku+'/upload/'+fileName);
readStream.pipe(req);
req.on('end', function() {
console.log("Image imageName="+fileName+" uploaded successfully.");
db.updateLastUploadedImage(sku,fileName).then(function (res) {
if(res) {
console.log("Table watches for sku="+sku+" updated.");
deferred.resolve(sku);
}
});
});
req.on('error',function(err) {
deferred.reject(err);
});
return deferred.promise;
}
I tried to bring it on with chaining the promises like documented in https://github.com/kriskowal/q but it's not working well. Somehow I do not come to the "then" block.
So I tried to make a recursive function but it also does not go inside the "then" block of the function call.
Only this method works but its not running through the correct order.
function uploadImages(sku) {
var promises = [];
for(var x=0; x<10; x++) {
promises.push(uploadImage(sku,(x+1)+".jpg")));
}
return Q.all(promises).then(function (res) {
return sku;
});
}
My recursive solution looks like this:
function uploadImages(sku,current,max) {
var deferred = Q.defer();
if(current<=max) {
uploadImage(sku,current+'.jpg').then(function (res) {
if(res) {
uploadImages(sku,current+1,max);
}
}, function (err) {
deferred.reject();
});
} else {
deferred.resolve(sku);
}
return deferred.promise;
}
What I'm looking for is something like this (but thats not the way to implement):
return uploadImage(sku,"1.jpg").then(function(res) {
return uploadImage(sku,"2.jpg").then(function(res) {
return uploadImage(sku,"3.jpg").then(function(res) {
return uploadImage(sku,"4.jpg").then(function(res) {
return uploadImage(sku,"5.jpg").then(function(res) {
return uploadImage(sku,"6.jpg").then(function(res) {
return uploadImage(sku,"7.jpg").then(function(res) {
return uploadImage(sku,"8.jpg").then(function(res) {
return uploadImage(sku,"9.jpg").then(function(res) {
return uploadImage(sku,"10.jpg").then(function(res) {
return sku;
});
});
});
});
});
});
});
});
});
});
So what is the best practice for my purpose?
There is no concept of "correct order" for async calls because they are just that -- asynchronous and they can end at any point.
In your the callback in Q.all(promises).then(...) you should have the responses in the order that you made them, but the order of your console logs may not be in the same order due their asynchronous nature.
In your case, you can probably do it recursively:
function uploadFiles(sku, current, max) {
return uploadImage(sku, current + '.jpg').then(function (sku) {
if (current > max) { return sku; }
return uploadFiles(sku, current + 1, max);
});
}
// use it
uploadFiles('SOME_SKU', 1, 10).then(function (sku) {
// ALL DONE!
});
Try to catch exceptions from the promise.
return Q.all(promises).then(function (res) {
return sku;
})
.catch(function (error) {
// Handle any error from all above steps
});
I have a small problem, this script works perfectly, with one problem, the "runTenant" method is not returning a promise (that needs resolving from "all()".
This code:
Promise.resolve(runTenant(latest)).then(function() {
end();
});
Calls this code:
function runTenant(cb) {
return new Promise(function() {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
new Tenant().fetchAll()
.then(function(tenants) {
if (tenants.models.length == 0) {
return;
} else {
async.eachSeries(tenants.models, function(tenant, next) {
var account = tenant.attributes;
Promise.resolve(db_tenant.config(account)).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
Promise.resolve(knex_pending).then(function() {
next(null, null);
});
} else {
next(null, null);
}
});
});
};
});
});
}
The code from runTenant is working correctly however it stalls and does not proceed to "end()" because the promise from "runTenant(latest)" isn't being resolved.
As if it weren't apparent, I am horrible at promises. Still working on getting my head around them.
Many thanks for any help/direction!
You should not use the Promise constructor at all here (and basically, not anywhere else either), even if you made it work it would be an antipattern. You've never resolved that promise - notice that the resolve argument to the Promise constructor callback is a very different function than Promise.resolve.
And you should not use the async library if you have a powerful promise library like Bluebird at hand.
As if it weren't apparent, I am horrible at promises.
Maybe you'll want to have a look at my rules of thumb for writing promise functions.
Here's what your function should look like:
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll().then(function(tenants) {
// if (tenants.models.length == 0) {
// return;
// } else
// In case there are no models, the loop iterates zero times, which makes no difference
return Promise.each(tenants.models, function(tenant) {
var account = tenant.attributes;
return db_tenant.config(account).then(function(knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
return cb(knex_tenant); // can return a promise
}
});
});
});
}
Your promise in runTenant function is never resolved. You must call resolve or reject function to resolve promise:
function runTenant() {
return new Promise(function(resolve, reject) {
// somewhere in your code
if (err) {
reject(err);
} else {
resolve();
}
});
});
And you shouldn't pass cb in runTenant function, use promises chain:
runTenant()
.then(latest)
.then(end)
.catch(function(err) {
console.log(err);
});
You need to return all the nested promises. I can't run this code, so this isn't a drop it fix. But hopefully, it helps you understand what is missing.
function runTenant(cb) {
//global var
if (!Tenant) {
loadCoreModels();
Tenant = bookshelf.core.bs.model('Tenant');
}
return new Tenant().fetchAll() //added return
.then(function (tenants) {
if (tenants.models.length == 0) {
return;
} else {
var promises = []; //got to collect the promises
tenants.models.each(function (tenant, next) {
var account = tenant.attributes;
var promise = Promise.resolve(db_tenant.config(account)).then(function (knex_tenant_config) {
if (knex_tenant_config) {
db_tenant.invalidateRequireCacheForFile('knex');
var knex_tenant = require('knex')(knex_tenant_config);
var knex_pending = cb(knex_tenant);
return knex_pending; //return value that you want the whole chain to resolve to
}
});
promises.push(promise); //add promise to collection
});
return Promise.all(promises); //make promise from all promises
}
});
}
I have a parse cloud code function, in this function I preform a query on some items then using a for loop I save some of those items. But the for loop continues and does not save some of the items before correctly.
Heres a general version of the code:
Parse.Cloud.define("saveCurrentDayItems", function(request, response) {
var xmlReader = require('cloud/xmlreader.js');
var MenuURL = Parse.Object.extend("MenuURL");
var queryMenuURL = new Parse.Query(MenuURL);
queryMenuURL.find().then(function(resultsMenuURL) {
//********************************************************************************************************
//I want the save to happen before it goes thought this for loop for the second time, and so on
for (var i = 0; i < resultsMenuURL.length; i++) {
var location = resultsMenuURL[i].get("Location");
Parse.Cloud.httpRequest({
url: url,
success: function(httpResponse) {
var xmlData = httpResponse.text;
xmlReader.read(xmlData, function (err, res){
if(err) return console.log(err);
for (var i3 = 0; i3 < res.menu.day.at(dayNumber).meal.count(); i3++) {
var meal = res.menu.day.at(dayNumber).meal.at(i3).attributes().name;
testItem.set("meal", meal);
testItem.set("location", location);
testItem.save().then(function(testItem) {
});
}
}
});
},
error: function(httpResponse) {
console.error('Request failed with response code ' + httpResponse.status);
}
});
}
});
});
I have looked at the parse docs, but I can't make sense of them, the promises section I just can't grasp.
Thanks so much for the help in advance
EDIT 2
When I have your code like this I get the error TypeError: Cannot call method 'reduce' of undefined
Parse.Cloud.define("saveCurrentDayItems23", function(request, response) {
var xmlReader = require('cloud/xmlreader.js');
//First worker function, promisify xmlReader.read();
function readResponse_async(xlmString) {
var promise = new Parse.Promise();
xmlReader.read(xlmString, function (err, res) {
if(err) {
promise.reject(err);
} else {
promise.resolve(res);
results = res;
}
});
return promise;
}
//Second worker function, perform a series of saves
function saveMeals_async(meals, location, testItem) {
return meals.reduce(function(promise, meal) {
return promise.then(function() {
testItem.set("meal", meal.attributes().name);
//the line above does not work it cannot get meal, it is undefined
testItem.set("location", location);
return testItem.save();
});
}, Parse.Promise.as());
}
var MenuURL = Parse.Object.extend("MenuURL");
var queryMenuURL = new Parse.Query(MenuURL);
//Master routine
queryMenuURL.find().then(function(resultsMenuURL) {
for (var i = 0; i < resultsMenuURL.length; i++) {
var url = resultsMenuURL[i].get('URL');
return resultsMenuURL.reduce(function(promise, item) {
return promise.then(function() {
return Parse.Cloud.httpRequest({
url: url,
//data: ... //some properties of item?
}).then(function(httpResponse) {
return readResponse_async(httpResponse.text).then(function() {
var TestItem = Parse.Object.extend("TestItem");
var testItem = new TestItem();
return saveMeals_async(result.menu.day.meal.counter.dish.name.text(),item.get("Location"),
testItem);
//this line above does not work, it sends only one string, not an array, so reduce cannot be called
});
});
});
}, Parse.Promise.as());
}
}).fail(function(err) {
console.error(err);
});
});
To do as the question asks ("I want the save to happen before it goes [through] this for loop for the second time, and so on"), is fairly involved - not really a beginners' problem.
It appears that you have several async operations here, viz :
queryMenuURL.find()
Parse.Cloud.httpRequest()
xmlReader.read()
testItem.save()
These operations need to work in cooperation with each other to give the desired effect.
queryMenuURL.find(), Parse.Cloud.httpRequest() and testItem.save() each appear to return a promise, while xmlReader.read() takes a node style callback, which makes things slightly awkward but not too bad.
You could write the code as one big block but you would end up with patterns within patterns. To make everything readable, you can pull out some of the code as (readabe) worker functions, leaving behind a (readable) master routine.
To convert your current outer for loop to set of sequential operations, you need the following pattern, which exploits Array.prototype.reduce() to build a .then chain, and returns a promise :
function doThings_async(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function(result) {
return doSomething_async(item, result);
});
}, resolvedPromise);
}
You will see below that this pattern is also used for the inner for loop, though other possibilities exist.
Parse.Cloud.define("saveCurrentDayItems", function(request, response) {
var xmlReader = require('cloud/xmlreader.js');
//First worker function, promisify xmlReader.read();
function readResponse_async(xlmString) {
var promise = new Parse.Promise();
xmlReader.read(xlmString, function (err, res) {
if(err) {
promise.reject(err);
} else {
promise.resolve(res);
}
}
return promise;
}
//Second worker function, perform a series of saves
function saveMeals_async(meals, location, testItem) {
return meals.reduce(function(promise, meal) {
return promise.then(function() {
testItem.set("meal", meal.attributes().name);
testItem.set("location", location);
return testItem.save();
});
}, Parse.Promise.as());
}
var MenuURL = Parse.Object.extend("MenuURL");
var queryMenuURL = new Parse.Query(MenuURL);
//Master routine
queryMenuURL.find().then(function(resultsMenuURL) {
...
return resultsMenuURL.reduce(function(promise, item) {
return promise.then(function() {
return Parse.Cloud.httpRequest({
url: url,
//data: ... //some properties of item?
}).then(function(httpResponse) {
return readResponse_async(httpResponse).then(function() {
return saveMeals_async(res.menu.day.at(dayNumber).meal, item.get("Location"), testItem);
});
});
});
}, Parse.Promise.as());
}).fail(function(err) {
console.error(err);
});
});
saveMeals_async() could be written to perform its saves in parallel rather than in series, but it depends on what you want. For parallel saves, only saveMeals_async() would need to be rewritten, using a different pattern.
EDIT
Revised code based on the edits in the question.
Due to changes in saveMeals_async(), the arr.reduce(...) pattern is now used only once in the master routine.
Parse.Cloud.define("saveCurrentDayItems", function(request, response) {
// ***
// insert all the Date/dayNumber code here
// ***
var xmlReader = require('cloud/xmlreader.js');
//First worker function, promisify xmlReader.read();
function readResponse_async(xlmString) {
var promise = new Parse.Promise();
xmlReader.read(xlmString, function (err, res) {
if(err) {
promise.reject(err);
} else {
promise.resolve(res);
}
}
return promise;
}
//Second worker function, perform a series of saves
function saveMeals_async(meals, school, diningHallNumber, menuLocation) {
//Declare all vars up front
var i3, i4, i5, m,
TestItem = Parse.Object.extend("TestItem"),//can be reused within the loops?
promise = Parse.Promise.as();//resolved promise to start a long .then() chain
for (i3 = 0; i3 < meals.count(); i3++) {
m = meals.at(i3);
//get number of stations in day
for (i4 = 0; i4 < m.counter.count(); i4++) {
//get number of items at each station
for (i5 = 0; i5 < m.counter.at(i4).dish.count(); i5++) {
//Here a self-executing function is used to form a closure trapping `testItem`.
//Otherwise the `testItem` used in `promise.then(...)` would be the last
//remaining `testItem` created when all iterations are complete.
(function(testItem) {
testItem.set("item", m.counter.at(i4).dish.at(i5).name.text());
testItem.set("meal", m.attributes().name);
testItem.set("school", school);
testItem.set("diningHallNumber", diningHallNumber);
testItem.set("schoolMenu", menuLocation);
//build the .then() chain
promise = promise.then(function() {
return testItem.save();
});
})(new TestItem());
});
}
}
return promise;
}
var MenuURL = Parse.Object.extend("MenuURL");
var queryMenuURL = new Parse.Query(MenuURL);
//Master routine
queryMenuURL.find().then(function(resultsMenuURL) {
return resultsMenuURL.reduce(function(promise, menuItem) {
var url = menuItem.get('URL'),
school = menuItem.get("school"),
diningHallNumber = menuItem.get("diningHallNumber"),
menuLocation = menuItem.get("menuLocation");
return promise.then(function() {
return Parse.Cloud.httpRequest({
url: url,
//data: ... //some properties of menuItem?
}).then(function(httpResponse) {
return readResponse_async(httpResponse).then(function(res) {
if (res.menu.day.at(dayNumber).meal) {
return saveMeals_async(res.menu.day.at(dayNumber).meal, school, diningHallNumber, menuLocation);
} else {
return Parse.Promise.as();//return resolved promise to keep the promise chain going.
}
});
});
});
}, Parse.Promise.as());
}).fail(function(err) {
console.error(err);
});
});
untested so may well need debugging
I have this class:
(function(){
"use strict";
var FileRead = function() {
this.init();
};
p.read = function(file) {
var fileReader = new FileReader();
var deferred = $.Deferred();
fileReader.onload = function(event) {
deferred.resolve(event.target.result);
};
fileReader.onerror = function() {
deferred.reject(this);
};
fileReader.readAsDataURL(file);
return deferred.promise();
};
lx.FileRead = FileRead;
}(window));
The class is called in a loop:
var self = this;
$.each(files, function(index, file){
self.fileRead.read(file).done(function(fileB64){self.fileShow(file, fileB64, fileTemplate);});
});
My question is, is there a way to call a method once the loop has completed and self.fileRead has returned it's deferred for everything in the loop?
I want it to call the method even if one or more of the deferred fails.
$.when lets you wrap up multiple promises into one. Other promise libraries have something similar. Build up an array of promises returned by fileRead.read and then pass that array to $.when and hook up then/done/fail/always methods to the promise returned by .when
// use map instead of each and put that inside a $.when call
$.when.apply(null, $.map(files, function(index, file){
// return the resulting promise
return self.fileRead.read(file).done(function(fileB64){self.fileShow(file, fileB64, fileTemplate);});
}).done(function() {
//now everything is done
})
var self = this;
var processFiles = function (data) {
var promises = [];
$.each(files, function (index, file) {
var def = data.fileRead.read(file);
promises.push(def);
});
return $.when.apply(undefined, promises).promise();
}
self.processFiles(self).done(function(results){
//do stuff
});
$.when says "when all these promises are resolved... do something". It takes an infinite (variable) number of parameters. In this case, you have an array of promises;
I know this is closed but as the doc states for $.when: In the multiple-Deferreds case where one of the Deferreds is rejected, jQuery.when immediately fires the failCallbacks for its master Deferred. (emphasis on immediately is mine)
If you want to complete all Deferreds even when one fails, I believe you need to come up with your own plugin along those lines below. The $.whenComplete function expects an array of functions that return a JQueryPromise.
var whenComplete = function (promiseFns) {
var me = this;
return $.Deferred(function (dfd) {
if (promiseFns.length === 0) {
dfd.resolve([]);
} else {
var numPromises = promiseFns.length;
var failed = false;
var args;
var resolves = [];
promiseFns.forEach(function (promiseFn) {
try {
promiseFn().fail(function () {
failed = true;
args = arguments;
}).done(function () {
resolves.push(arguments);
}).always(function () {
if (--numPromises === 0) {
if (failed) {
//Reject with the last error
dfd.reject.apply(me, args);
} else {
dfd.resolve(resolves);
}
}
});
} catch (e) {
var msg = 'Unexpected error processing promise. ' + e.message;
console.error('APP> ' + msg, promiseFn);
dfd.reject.call(me, msg, promiseFn);
}
});
}
}).promise();
};
To address the requirement, "to call the method even if one or more of the deferred fails" you ideally want an .allSettled() method but jQuery doesn't have that particular grain of sugar, so you have to do a DIY job :
You could find/write a $.allSettled() utility or achieve the same effect with a combination of .when() and .then() as follows :
var self = this;
$.when.apply(null, $.map(files, function(index, file) {
return self.fileRead.read(file).then(function(fileB64) {
self.fileShow(file, fileB64, fileTemplate);
return fileB64;//or similar
}, function() {
return $.when();//or similar
});
})).done(myMethod);
If it existed, $.allSettled() would do something similar internally.
Next, "in myMethod, how to distinguish the good responses from the errors?", but that's another question :)