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
}
Related
I'm struggling to get my head around this. I've searched for similar problems.
Basically, I have 2 asynchronous functions. Let's call them getData() and processData() for the purposes of this question, which both return a promise. (these 2 functions are not defined using the async keyword)
I then have a function which calls these 2 using the async keyword. Like so:
async function init() {
// get the data
for (var i = 0; i < arr_process.length; i++) {
try {
await getData(arr_process[i]);
} catch (err) {
return Promise.reject(err);
}
}
// now process the data
for (var i = 0; i < arr_done.length; i++) {
try {
await processData(arr_done[i]);
} catch (err) {
return Promise.reject(err);
}
}
}
My question is; is this the correct way to handle promise rejections. My understanding is as soon as you define a function using the async keyword it will return a promise. I want to reject that promise if either of the functions using the await keyword (getData or processData) reject a promise - and I don't want the remainder of the function to execute. So if the getData promise rejects above, I don't want the loop to continue or the second loop to start - I want to exit the function returning a rejected promise. Is this the correct implementation of what I'm after? Hopefully that all makes sense!
All the try/catches are unnecessary. If a Promise that is awaited rejects, the whole containing async function will reject, and the rejection value will be the thrown error. You may simply do:
async function init() {
// get the data
for (var i = 0; i < arr_process.length; i++) {
await getData(arr_process[i]);
}
// now process the data
for (var i = 0; i < arr_done.length; i++) {
await processData(arr_done[i]);
}
}
and then, just like with your original code, the caller of init will catch any errors with a catch:
init()
.catch((err) => {
console.log('error:', err);
});
You can throw an error from within an async function by simply using the throw keyword.
For example:
async function init() {
throw new Error('error message')
}
or in your case, just remove the try catch blocks and any errors will be thrown automatically.
async function init() {
// get the data
for (var i = 0; i < arr_process.length; i++) {
await getData(arr_process[i]);
}
// now process the data
for (var i = 0; i < arr_done.length; i++) {
await processData(arr_done[i]);
}
}
I'm processing a list in a loop that's run async returning a promise and I do not want to exit processing on exception, so I aggregate them and pass them to the resolve callback in an outer finally block.
I'll like to know if this is an anti-pattern and if so, please provide a pointer on how to do correctly.
Thanks.
Example
async doSomething(list) {
let errorCount = 0
let errors = []
return new Promise(async (resolve, reject) => {
try {
list.forEach(async (item) => {
try {
actionThatThrows(item)
} catch (e) {
errorCount++
errors[errorCount] = e
}
})
} catch (e) {
errorCount++
errors[errorCount] = e
} finally {
if (errorCount > 0) {
resolve(errors)
} else {
resolve()
}
}
})
}
Yes, this code employs several antipatterns:
Never pass an async function to a Promise constructor
Never use the Promise constructor when you already have promises around
forEach does not work with async functions
I do not want to exit processing on exception but aggregate them
You might want to have a look at Wait until all ES6 promises complete, even rejected promises for that.
But you can do it without those as well, assuming you want sequential iteration:
async function doSomething(list) {
const errors = [];
for (let item of list) {
try {
await actionThatThrows(item);
} catch (e) {
errors.push(e);
}
}
if (errors.length)
return errors;
else
return …;
}
The errors are the result of your asynchronous computation so it globally looks legit.
Assuming that actionThatThrows returns a promise (it's unclear in your question and code), it looks like it could be written like this:
function doSomething(list) {
let errors = []
return Promise.all(list.map(
item => actionThatThrows(item).catch(e => {
errors.push(e);
})
)).then(()=>{
return errors.length ? errors : undefined;
});
}
1) async doSomething is not invoking an await, so remove async
2) async in list.forEach is not invoking an await, so remove async
3) First catch will catch all. Second catch will never be hit, so remove second catch and finally
Code can be simplified to:
doSomething(list) {
let errorCount = 0,
errors = [];
for (let item of list) {
try {
actionThatThrows(item); //I suppose this is not returning a promise
} catch (e) {
errorCount += 1;
errors[errorCount] = e;
}
}
if (errorCount > 0) {
return errors; //return Promise.reject(errors);
} else {
//return Promise.resolve();
}
}
I'm trying to make an asynchronous loop with native ES6 promises It kind of works, but incorrectly. I suppose I made a huge mistake somewhere and I need someone to tell me where it is and how it's done correctly
var i = 0;
//creates sample resolver
function payloadGenerator(){
return function(resolve) {
setTimeout(function(){
i++;
resolve();
}, 300)
}
}
// creates resolver that fulfills the promise if condition is false, otherwise rejects the promise.
// Used only for routing purpose
function controller(condition){
return function(resolve, reject) {
console.log('i =', i);
condition ? reject('fin') : resolve();
}
}
// creates resolver that ties payload and controller together
// When controller rejects its promise, main fulfills its thus exiting the loop
function main(){
return function(resolve, reject) {
return new Promise(payloadGenerator())
.then(function(){
return new Promise(controller(i>6))
})
.then(main(),function (err) {
console.log(err);
resolve(err)
})
.catch(function (err) {
console.log(err , 'caught');
resolve(err)
})
}
}
new Promise(main())
.catch(function(err){
console.log('caught', err);
})
.then(function(){
console.log('exit');
process.exit()
});
Now the output:
/usr/local/bin/iojs test.js
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
fin
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
caught [TypeError: undefined is not a function]
exit
Process finished with exit code 0
The good part: it reaches the end.
The bad part: it catches some errors and I don't know why.
Any helper function with promise looping I have seen actually made it much worse than what you can do out of the box with recursion.
It is a little nicer with .thenReturn but yeah:
function readFile(index) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Read file number " + (index +1));
resolve();
}, 500);
});
}
// The loop initialization
Promise.resolve(0).then(function loop(i) {
// The loop check
if (i < len) { // The post iteration increment
return readFile(i).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
See it in jsfiddle http://jsfiddle.net/fd1wc1ra/
This is pretty much exactly equivalent to:
try {
for (var i = 0; i < len; ++i) {
readFile(i);
}
console.log("done");
} catch (e) {
console.log("error", e);
}
If you wanted to do nested loops it is exactly the same:
http://jsfiddle.net/fd1wc1ra/1/
function printItem(item) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Item " + item);
resolve();
}, 500);
});
}
var mdArray = [[1,2], [3,4], [5,6]];
Promise.resolve(0).then(function loop(i) {
if (i < mdArray.length) {
var array = mdArray[i];
return Promise.resolve(0).then(function innerLoop(j) {
if (j < array.length) {
var item = array[j];
return printItem(item).thenReturn(j + 1).then(innerLoop);
}
}).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
If all you're trying to do is count to 7 with promises, then this will do it:
function f(p, i) {
return p.then(function() {
return new Promise(function(r) { return setTimeout(r, 300); });
})
.then(function() { console.log(i); });
}
var p = Promise.resolve();
for (var i = 0; i < 8; i++) {
p = f(p, i);
}
p.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
Looping with promises is hard, because it's almost impossible not to fall into JavaScript's closures in a loop trap, but it is doable. The above works because it pushes all use of .then() into a sub-function f of the loop (i.e. away from the loop).
A safer solution, that I use, is to forgo loops altogether and seek out patterns like forEach and reduce whenever I can, because they effectively force the sub-function on you:
[0,1,2,3,4,5,6,7].reduce(f, Promise.resolve())
.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
here f is the same function as above. Try it.
Update: In ES6 you can also use for (let i = 0; i < 8; i++) to avoid the "closures in a loop" trap without pushing code into a sub-function f.
PS: The mistake in your example is .then(main(), - it needs to be .then(function() { return new Promise(main()); }, but really, I think you're using the pattern wrong. main() should return a promise, not be wrapped by one.
Try logging err.stack instead of just err when catching promise errors.
In this case, it looks like resolve and reject are not defined within the anonymous function that gets return from main after the initial iteration is complete. I can't totally follow your control flow, but that seems to make sense - after the 7 iterations are complete, there should no longer be any new promises. However, it seems like the code is still trying to run like there are more promises to resolve.
Edit: This is the problem .then(main(),function (err) {. Invoking main on its own will cause resolve and reject inside the anonymous function to be undefined. From the way I read it, main can only be invoked as an argument to the Promise constructor.
I had a similar need and tried the accepted answer, but I was having a problem with the order of operations. Promise.all is the solution.
function work(context) {
return new Promise((resolve, reject) => {
operation(context)
.then(result => resolve(result)
.catch(err => reject(err));
});
}
Promise
.all(arrayOfContext.map(context => work(context)))
.then(results => console.log(results))
.catch(err => console.error(err));
I've looked around for various solutions too and couldn't find one that satisfied me, so I ended up creating my own. Here it is in case it's useful to someone else:
The idea is to create an array of promise generators and to give this array to a helper function that's going to execute the promises one after another.
In my case the helper function is simply this:
function promiseChain(chain) {
let output = new Promise((resolve, reject) => { resolve(); });
for (let i = 0; i < chain.length; i++) {
let f = chain[i];
output = output.then(f);
}
return output;
}
Then, for example, to load multiple URLs one after another, the code would be like this:
// First build the array of promise generators:
let urls = [......];
let chain = [];
for (let i = 0; i < urls.length; i++) {
chain.push(() => {
return fetch(urls[i]);
});
}
// Then execute the promises one after another:
promiseChain(chain).then(() => {
console.info('All done');
});
The advantage of this approach is that it creates code that's relatively close to a regular for loop, and with minimal indentation.
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 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);
});