I have a function that returns a value asynchronously using the Q library.
And I have to call it inside a loop to fill an array with all the results.
Something like this example I've been working on (this is not working as intented, since the array is returned before being filled).
function fillArray(){
var deferred = q.defer();
var resultsArray;
for(var i=0; i < numberOfElements; i++){
asyncFunction(i).then(function(result){
resultsArray.push(result);
}
}
deferred.resolve(resultsArray);
return deferred.promise;
}
function asyncFunction(number){
var deferred = q.defer();
deferred.resolve(number+1);
return deferred.promise;
}
I've read that I can use Q.all to call several functions, but I cannot understand how to call it inside the loop.
Thanks for the help.
Try this:
function fillArray(){
var promises = [];
for(var i = 0; i < numberOfElements; i++){
promises.push(asyncFunction(i));
}
return q.all(promises);
}
function asyncFunction(number){
var deferred = q.defer();
deferred.resolve(number+1);
return deferred.promise;
}
promise.all() is rejected with the same rejection reason as the first promise to be rejected. This means that if some of the promises in the array are rejected, the promise returned by all is also rejected. You can use promise.allSettled() if you want to wait for all the original promises to be settled.
I did this
fillArray().then(function(result){
console.log(result);
});
and it worked correct.
Only thing I did extra from your code is changing
var resultsArray = [];
Related
I'm trying to generalize a script that encrypts forms using OpenPGP libraries.
I got some troubles with the client-side code (Javascript) :
var working = formID.elements[0];
var counter = 0;
while (working) {
encrypt(working.value).then(function(encrypted_msg) {
console.log("processing");
working.value = encrypted_msg;
});
console.log("assuming processed");
var counter = counter + 1;
var working = formID.elements[counter];
}
The following code should take each form element and encrypt its value. However, the while loop doesn't wait for the asynchronous encrypt() function to be resolved.
I think i need to use promises in this case, but i have no idea how and the few tutorials didn't work in a while loop.
Help ?
Probably can be used list of jQuery deferreds, something like this:
var deferreds = [];
$.each(formID.elements, function(key, working){
var deferred = $.Deferred();
deferreds.push(deferred);
encrypt(working.value).then(function(encrypted_msg) {
console.log("processing");
working.value = encrypted_msg;
deferred.resolve();
});
});
$.when.apply( $, deferreds ).done(function(){
console.log( 'after all encryptions!' );
});
Of course, can be used native Promise object instead $.Deferred, however I think $.Deferred is more cross-browser way.
UPD2:
Improved answer based on native Promise and Promise.resolve() (thanks to #Bergi). For the case when encrypt() returns correct promise, method Promise.resolve() can be skipped.
var promises = [];
$.each(formID.elements, function(key, working){
var promise = Promise.resolve(encrypt(working.value))
.then(function(encrypted_msg) {
console.log("processing");
working.value = encrypted_msg;
});
promises.push(promise);
});
Promise.all(promises).then(function(){
console.log( 'after all encryptions!' );
});
var iterator = [];
for (var counter = 0; counter < formID.elements.length; counter++) {
var working = formID.elements[counter];
iterator.push(encrypt(working.value));
}
Promise.all(iterator)
.then(fumction(data){
//Here you have all data
})
You can synchronize your operation like this way. By collecting all asynchronus value references and point to them when they have data.
In case your data is dependent.
function myfunction(previousValue){
if(breaking Condition){
return Promise.resolve();
}
return encrypt(working.value).then(function(encrypted_msg) {
working.value = encrypted_msg;
return myfunction(working);
});
}
I have been studying promises through this link and I understood the idea of it
var parentID;
$http.get('/api/user/name')
.then(function(response) {
parentID = response.data['ID'];
for (var i = 0; i < response.data['event-types'].length; i++) {
return $http.get('/api/security/' + response.data['event-types'][i]['key']);
}
})
.then(function(response) {
// response only returns one result of the many promises from the for loop
// do something with parentID;
});
However, my use case requires to loop through and send create more than 1 promises. I have tried to chain an as example above but only the only one of the promise created from the for loop was executed.
How can I continue chaining all of the promises while continue having access to the variable parentID?
You should use $q.all because it is integrated with the AngularJS digest cycle.
var parentID;
$http.get('/api/user/name')
.then(function(response) {
parentID = response.data['ID'];
var promiseList = [];
for (var i = 0; i < response.data['event-types'].length; i++) {
var iPromise = $http.get('/api/security/' + response.data['event-types'][i]['key']);
promiseList.push(iPromise);
};
return $q.all(promiseList);
})
.then(function(responseList) {
console.log(responseList);
});
From the Docs:
all(promises);
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Parameters
An array or hash of promises.
Returns
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
--AngularJS $q Service API Reference -- $q.all
You can utilize Promise.all(), substitute Array.prototype.map() for for loop
var parentID;
$http.get('/api/user/name')
.then(function(response) {
parentID = response.data['ID'];
return Promise.all(response.data['event-types'].map(function(_, i) {
return $http.get('/api/security/' + response.data['event-types'][i]['key'])
}))
})
.then(function(response) {
// response only returns one result of the many promises from the for loop
// do something with parentID;
})
.catch(function(err) {
console.log(err);
});
Yet another question about promises. I have this scenario:
Service.prototype.parse = function (data) {
var deferred = $.Deferred();
var arr = [];
for (var i = 0; i < data.length; i++) {
var details = new Details();
$.when(details).then(function (data) {
arr.push(data);
deferred.resolve(arr);
});
}
return deferred.promise;
};
Somewhere else in the code:
...
$.when(parse()).then(function (resp) {
//...
});
The promises get resolved at some point but initially resp has a length of 1.
How to wait for parse() to resolve everything and return a array?
No need for a deferred anti pattern (explicit construction) or for explicit array construction. Your code can be simplified to:
Service.prototype.parse = function (data) {
return $.when.apply($, data.map(function(x){
return new Details();
}).then(function(){ return arguments; });//.then(Array.of); // instead, if want array
};
Some general advice:
You're ignoring the item you're iterating in the data, I assume you don't do that in your real code but just making sure.
It's generally not the best idea to perform async IO in a constructor. Please consider separating construction and initialization.
You should consider using native promises (or a library) or using jQuery 3.0.0 which supports non problematic promises.
Please try this:
Service.prototype.parse = function(data) {
var detailsArr = [];
for (var i = 0; i < data.length; i++) {
var details = new Details();
detailsArr.push(details);
}
return $.when.apply($, detailsArr).then(function(){
return Array.prototype.slice.call(arguments);
});
};
Here we put all Details into an array, use $.when.apply($, arr) to wait for all Details get resolved. When it's done, each Details' return data will be passed to callback function as one parameter, so the call back function will receive total data.length number of parameters. Then we use Array.prototype.slice.call to convert all parameters to an array and return the result.
For your reference:
What does $.when.apply($, someArray) do?
How can I convert the "arguments" object to an array in JavaScript?
I am currently trying to build a File Uploader using the HTML5 FileAPI. The File Uploader is supposed to handle multiple files and show image previews if the file is an image.
since the FileReader class works asynchronously I want to wait until all the the files have been read. Therefore I am using Deferreds.
A method which reads the file is returning a promise. Another method loops through all files and pushes all promises into an array. Then I'm applying the then() method once all promises have been added to my array.
Now to my problem. Since the then() method is only called once, when I got all promises. I have no chance to process every single promise. What I want to do is, to loop through all my promises once they are all there and return the result.
This is my FileProcessor Object
read: function(file) {
var reader = new FileReader();
var deferred = $.Deferred();
reader.onload = function(event){
deferred.resolve(event.target.result);
};
reader.onerror = function() {
deferred.reject(this);
}
if(/^image/.test(file.type))
reader.readAsDataURL(file);
return deferred.promise();
},
And here comes the FileManager Object's handleFileSelect() method that is supposed to call the FileProcessor.read() method.
handleFileSelect: function(e){
var $fileList = $('#file-list');
var files = e.target.files;
var promises = []; //Promises are going to be stored here
var filestring = '';
var processedFile;
// Loop trough all selected files
for(var i = 0; i < files.length; i++){
// Store the promise in a var...
processedFile = FileProcessor.read(files[i]);
// And push it into the array where the promises are stored
promises.push(processedFile);
}
// Once all deferreds have been fired...
$.when.apply(window, promises).then(function(){
for(var i = 0; i < promises.length; i++){
// PROBLEM HERE: I need to get the
// result from my resolved Deferred
// of every single promise object
console.log(promises[i]);
}
});
},
Am I using the wrong approach for deferreds and promises? Aren't they supposed to be used like I'm trying to do and is there a more elegant way to achieve my purpose?
Am I using the wrong approach for deferreds and promises?
Yes. In the asynchronous callback you should not access promises, but only the callback arguments. The return promise of $.when does resolve with an array of the .resolve() parameters for each of the promises in the array - you can loop over the arguments object to access the results:
$.when.apply($, promises).then(function () {
for (var i = 0; i < arguments.length; i++) {
var singleresult = arguments[i][0];
console.log(singleresult);
}
});
you can use the arguments object to refer all the values returned by the promise objects
$.when.apply($, promises).then(function () {
for (var i = 0; i < arguments.length; i++) {
var data = arguments[i][0];
// PROBLEM HERE: I need to get the
// result from my resolved Deferred
// of every single promise object
console.log(data);
}
});
I see you heve accepted an answer but you might like to be aware that your handleFileSelect method can be significantly simplified as follows
handleFileSelect: function(e) {
var promises = $.map(e.target.files, function(file) {
return FileProcessor.read(file);
});
$.when.apply(window, promises).then(function() {
$.each(arguments, function(i, a) {
console.log(a[0]);
});
});
},
Of course it will get more complicated again when you flesh out the result handling but this should give you a nice concise basis.
I am currently trying to build a File Uploader using the HTML5 FileAPI. The File Uploader is supposed to handle multiple files and show image previews if the file is an image.
since the FileReader class works asynchronously I want to wait until all the the files have been read. Therefore I am using Deferreds.
A method which reads the file is returning a promise. Another method loops through all files and pushes all promises into an array. Then I'm applying the then() method once all promises have been added to my array.
Now to my problem. Since the then() method is only called once, when I got all promises. I have no chance to process every single promise. What I want to do is, to loop through all my promises once they are all there and return the result.
This is my FileProcessor Object
read: function(file) {
var reader = new FileReader();
var deferred = $.Deferred();
reader.onload = function(event){
deferred.resolve(event.target.result);
};
reader.onerror = function() {
deferred.reject(this);
}
if(/^image/.test(file.type))
reader.readAsDataURL(file);
return deferred.promise();
},
And here comes the FileManager Object's handleFileSelect() method that is supposed to call the FileProcessor.read() method.
handleFileSelect: function(e){
var $fileList = $('#file-list');
var files = e.target.files;
var promises = []; //Promises are going to be stored here
var filestring = '';
var processedFile;
// Loop trough all selected files
for(var i = 0; i < files.length; i++){
// Store the promise in a var...
processedFile = FileProcessor.read(files[i]);
// And push it into the array where the promises are stored
promises.push(processedFile);
}
// Once all deferreds have been fired...
$.when.apply(window, promises).then(function(){
for(var i = 0; i < promises.length; i++){
// PROBLEM HERE: I need to get the
// result from my resolved Deferred
// of every single promise object
console.log(promises[i]);
}
});
},
Am I using the wrong approach for deferreds and promises? Aren't they supposed to be used like I'm trying to do and is there a more elegant way to achieve my purpose?
Am I using the wrong approach for deferreds and promises?
Yes. In the asynchronous callback you should not access promises, but only the callback arguments. The return promise of $.when does resolve with an array of the .resolve() parameters for each of the promises in the array - you can loop over the arguments object to access the results:
$.when.apply($, promises).then(function () {
for (var i = 0; i < arguments.length; i++) {
var singleresult = arguments[i][0];
console.log(singleresult);
}
});
you can use the arguments object to refer all the values returned by the promise objects
$.when.apply($, promises).then(function () {
for (var i = 0; i < arguments.length; i++) {
var data = arguments[i][0];
// PROBLEM HERE: I need to get the
// result from my resolved Deferred
// of every single promise object
console.log(data);
}
});
I see you heve accepted an answer but you might like to be aware that your handleFileSelect method can be significantly simplified as follows
handleFileSelect: function(e) {
var promises = $.map(e.target.files, function(file) {
return FileProcessor.read(file);
});
$.when.apply(window, promises).then(function() {
$.each(arguments, function(i, a) {
console.log(a[0]);
});
});
},
Of course it will get more complicated again when you flesh out the result handling but this should give you a nice concise basis.