What I've been trying is to call $.ajax multiple times, until the result is empty.
Here is a simplified version of my experimentation...
var objects = [];
function getObjects (page){
return new Promise(function(resolve, reject){
$.ajax({
url : url+"&pageNumber="+page,
type:'get',
dataType:'json',
async : true,
}).done(function(results){
if(results.length > 0){
objects = objects.concat(results);
return getObjects(page + 1);
}
console.log("Finished",objects);
resolve(objects);
}).fail(reject);
});
};
var page = 0;
getObjects(page).then(function(objects) {
console.log("getObjects completed",objects);
}).catch(function(error){
console.log(error);
});
Put simply, getObjects(page) will be called repeatedly until all the objects are retrieved.
The first console.log() shows what I expect, but the then function at the end doesn't run.
Interestingly, when I intentionally make the function fail, the catch part works properly. Clearly, resolve isn't working, but I don't see why.
Any advice will be appreciated.
EDIT
I've tried reject and reject(objects), neither worked.
jQuery Ajax requests are promises. They may not be Promise instances, but they implement the Promise interface. There is no need to wrap them.
function getObjects(page, objects) {
page = page || 0;
objects = objects || [];
return $.get(url, {pageNumber: page}).then(function (results) {
objects.push.apply(objects, results);
return results.length ? getObjects(page + 1, objects) : objects;
});
}
getObjects().done(function (objects) {
console.log("getObjects completed",objects);
}).fail(function(error){
console.log(error);
});
Also, in this case there is no reason to use the more wordy $.ajax(). $.get() will do just fine. jQuery will automatically detect a JSON reponse, and Ajax requests are async anyway, so setting the dataType and async parameters is redundant.
Related
I'm new on AngularJS and JavaScript.
I am getting remote information for each of the elements of an array (cars) and creating a new array (interested prospects). So I need to sync the requests. I need the responses of each request to be added in the new array in the same order of the cars.
I did it first in with a for:
for (a in cars) {
//async request
.then(function () {
//update the new array
});
}
This make all the requests but naturally didn't update the new array.
After seeking in forums, I found this great examples and explanations for returning a intermediate promise and sync all of them.
1. http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
2. http://stackoverflow.com/questions/25605215/return-a-promise-from-inside-a-for-loop
3. http://www.html5rocks.com/en/tutorials/es6/promises/
(#MaurizioIndenmark, #Mark Rajcok , #Michelle Tilley, #Nolan Lawson)
I couldn't use the Promise.resolve() suggested in the second reference. So I had used $q.defer() and resolve(). I guess I have to inject a dependency or something else that I missed. As shown below:
In the Controller I have:
$scope.interestedProspects = [] ;
RequestDetailsOfAsync = function ($scope) {
var deferred = $q.defer();
var id = carLists.map(function (car) {
return car.id;
}).reduce(function (previousValue, currentValue) {
return previousValue.then(function () {
TheService.AsyncRequest(currentValue).then(function (rData) {
$scope.interestedProspects.push(rData);
});
});
}, deferred.resolve());
};
In the Service I have something like:
angular.module('app', []).factory('TheService', function ($http) {
return {
AsyncRequest = function (keyID) {
var deferred = $q.defer();
var promise = authorized.get("somep.provider.api/theService.json?" + keyID).done(function (data) {
deferred.resolve(data);
}).fail(function (err) {
deferred.reject(err);
});
return deferred.promise;
}
}
}
The displayed error I got: Uncaught TypeError: previousValue.then is not a function
I made a jsfiddle reusing others available, so that it could be easier to solve this http://jsfiddle.net/alisatest/pf31g36y/3/. How to wait for AsyncRequests for each element from an array using reduce and promises
I don't know if the mistakes are:
the place where the resolve is placed in the controller function.
the way the reduce function is used
The previousValue and currentValue sometimes are seen by javascript like type of Promise initially and then as a number. In the jsfiddle I have a working example of the use of the reduce and an http request for the example.
Look at this pattern for what you want to do:
cars.reduce(function(promise, car) {
return promise.then(function(){
return TheService.AsyncRequest(car).then(function (rData) {
$scope.details.push(rData);
});
});
}, $q.when());
This will do all the asynchronous calls for every car exactly in the sequence they are in the cars array. $q.all may also be sufficient if the order the async calls are made doesn't matter.
It seems you are calling reduce on an array of ids, but assume in the passed function that you are dealing with promises.
In general, when you want to sync a set of promises, you can use $q.all
You pass an array of promises and get another promise in return that will be resolved with an array of results.
I'm a bit confused I am using the result of callone() to modify a global object(I'm not sure of a better way to do this) trying to accomplish this with deferred. By the time time I calltwo() the global object should be modified with the new data
var obj = {};
var id = obj.id;
//global object
$.when(callone(obj)).then(calltwo(id),function(data)
{
});
- Ajax function :1
function callone(requiredData)
{
var d = new $.Deferred();
var ajaxCall1 = $.ajax({
type:"POST",
url: 'AB/',
data: requiredData,
success: function(data) {
//return data to the callee?
d.resolve(p_obj);
//set ID on the object
obj.id = data.id;
return obj;
},
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ': ' + errorThrown);
},
always: function(data) { }
});
}
function calltwo(id from callback one)
{
}
I've included a much simpler implementation below.
callone() must return a deferred or promise for you to wait on it or chain other operations to it and you can just use the promise that $.ajax() already returns rather than creating your own.
Further, there is no reason to use $.when() here because it really only adds value when you're trying to wait on multiple promises running in parallel which isn't your case at all. So, you can just use the .then() handler on the individual promises you already have.
In addition, you really don't want to use globals when processing async operations. You can chain the promises and pass the data right through the promises.
Here's what callone() should look like:
function callone(requiredData) {
return $.ajax({
type: "POST",
url: 'AB/',
data: requiredData
});
}
function calltwo(...) {
// similar to callone
// returns promise from $.ajax()
}
callone(...).then(function(data) {
// when callone is done, use the id from its result
// and pass that to calltwo
return calltwo(data.id);
}).then(function(data) {
// process result from calltwo here
}, function(err) {
// ajax error here
});
Notice that this code isn't creating any new Deferred objects. It's just using the promise that is already returned from $.ajax(). Note also that it isn't use success: or error: handlers either because those also come through the promises.
Also, note that return a promise from within a .then() handler automatically chains it into the previous promise so the previous promise won't be resolved until the newly returned promise is also resolved. This allows you to keep the chain going.
Also, note that returning data from an async callback function does not return data back to the caller of the original function so your attempt to return something from the success: handler was not accomplishing anything. Instead, use promises and return data through the promises as they are specifically designed to get async data back to the .then() handlers.
Below are my Multiple ajax calls with promises.
$(window).load(function(){
$.when(getApiModemList()).done(function(vendors){
var deferreds = calculateApiBalances(vendors);
$.when.apply($,deferreds).done(function(balance) {
console.log(balance);
console.log("All Done");
});
});
function getApiModemList(){
return $.getJSON("url");
}
function calculateApiBalances(vendors)
{
var defer=[];
$.each(vendors,function(k,v){
defer.push($.getJSON(someurl));
});
return defer;
}
});
Function calculateApiBalances() return me some balance which I need to Sum up to get total of all balance .
But when print console.log(balance) it doesnot provide me with valid array of balance json.
Another issue is if any one of my ajax calls in calculateApiBalances() fails it doesnot prints All Done.
What should be done in above code to achieve this.
But when print console.log(balance) it doesnot provide me with valid array of balance json.
That's a weird thing of $.when. It doesn't provide you with an array, but calls your callback with multiple arguments.
Another issue is if any one of my ajax calls in calculateApiBalances() fails it doesnot prints All Done.
Yes, because when one promise fails the whole $.when() promise is rejected immediately, and you don't have any error handler. You'll have to catch errors individually if you want to always get an array (of possibly invalid responses). See also $.Deferred: How to detect when every promise has been executed.
What should be done in above code to achieve this.
First of all, avoid the deferred antipattern :-)
$(window).load(function() {
getApiModemList().then(calculateApiBalances).then(function() {
var balance = Array.prototype.slice.call(arguments);
console.log(balance);
console.log("All Done");
});
});
function getApiModemList() {
return $.getJSON(somurl).then(function(res) {
return res.data;
});
}
function calculateApiBalances(vendors) {
return $.when.apply($, $.map(vendors, function(v, k) {
return $.getJSON(someurl).then(null, $.when);
}));
}
EDIT by Roamer:
And here's a version of the master routine with a mechanism for summing the balances.
getApiModemList().then(calculateApiBalances).then(function() {
var sumOfBalances = Array.prototype.reduce.call(arguments, function(tot, obj) {
return tot + (+obj.balance || 0);
}, 0);
console.log(sumOfBalances);
console.log("All Done");
});
obj is the object promised by $.getJSON(someurl) in calculateApiBalances(). In the case of a $.getJSON() error, obj will be a jqXHR object, obj.balance will be undefined and +obj.balance will be NaN, therefore default to adding zero; otherwise add obj.balance .
If you wanted to know how many of the getJSON requests were successful and how many were unsuccessful, then there's some more code to write, but not a lot.
I think this is a really stupid question but I'm having a hard time wrapping my head around promises.
I'm using Q (for nodejs) to sync up a couple of async functions.
This works like a charm.
var first = function () {
var d = Q.defer();
fs.readdir(path,function(err,files){
if(err) console.log(err);
d.resolve(files);
});
return d.promise;
};
var second = function (files) {
var list = new Array;
files.forEach(function(value, index){
var d = Q.defer();
console.log('looking for item in db', value);
db.query(
'SELECT * FROM test WHERE local_name =? ', [value],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
d.resolve(rows[0]);
} else {
var itemRequest = value;
getItemData(itemRequest);
}
}
);
list.push(d.promise);
});
return Q.all(list);
};
first()
.then(second)
.done(function(list){
res.send(list);
});
The problem I have is with this little function:
getItemData(itemRequest)
This function is filled with several of callbacks. The promise chain runs through the function just fine but ignores all the callbacks I use ( eg several XHR calls I make in the function).
A simplified version of the function looks like this (just to give you an idea):
function getItemData(itemRequest){
helper.xhrCall("call", function(response) {
var requestResponse = JSON.parse(response)
, requestInitialDetails = requestResponse.results[0];
downloadCache(requestInitialDetails,function(image) {
image = localImageDir+requestInitialDetails.image;
helper.xhrCall("call2", function(response) {
writeData(item,image,type, function(){
loadData(item);
});
});
} else {
writeData(item,image,type, function(){
loadData(item);
});
}
});
});
The xhr function I use looks like this:
xhrCall: function (url,callback) {
var request = require("request")
, colors = require('colors');
request({
url: url,
headers: {"Accept": "application/json"},
method: "GET"
}, function (error, response, body) {
if(!error){
callback(body);
}else{
console.log('Helper: XHR Error',error .red);
}
});
}
So my questions:
Can I leave the function unaltered and use the callbacks that are in place ánd the promise chain?
Or do I have to rewrite the function to use promises for the XHR?
And if so, How can I best write my promise chain? Should I reject the initial promise in the forEach?
Again, sorry if this is a really stupid question but I don't know what the right course of action is here.
Thanks!
[EDIT] Q.nfcall, I don't get it
So I've been looking into Q.nfcall which allows me to use node callbacks. Bu I just don't understand exacly how this works.
Could someone give a simple example how I would go about using it for a function with several async xhr calls?
I tried this but as you can see I don't really understand what I'm doing:
var second = Q.nfcall(second);
function second (files) {
[EDIT 2]
This is the final funcction in my getitemdata function callback chain. This function basically does the same as the function 'second' but I push the result directly and then return the promise. This works as stated, but without all the additional callback data, because it does not wait for the callbacks to return with any data.
function loadData(item) {
var d = Q.defer();
db.query(
'SELECT * FROM test WHERE local_name =? ', [item],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
list.push(d.promise);
}
}
);
});
return Q.all(list);
};
Your answer is not really clear after your second edit.
First, on your orignal question, your getItemData has no influence on the promise chain.
You could change you the function's call signature and pass your deferred promise like so.
getItemData(itemRequest, d)
and pass this deferred promises all the way to your xhrCall and resolve there.
I would re-write your whole implementation and make sure all your functions return promises instead.
Many consider deferred promises as an anti-pattern. So I use use the Promise API defined in harmony (the next javascript)
After said that, I would re-implement your original code like so (I've not tested)
var Promise = Promise || require('es6-promise').Promise // a polyfill
;
function errHandler (err){
throw err
}
function makeQuery () {
var queryStr = 'SELECT * FROM test WHERE local_name =? '
, queryOpt = {local_name: String}
;
console.log('looking for item in db', value)
return new Promise(function(resolve, reject){
db.query(queryStr, [value], queryOpt, function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
resolve(rows[0]);
} else {
// note that it returns a promise now.
getItemData(value).then(resolve).catch(errHandler)
}
})
})
}
function first () {
return new Promise(function(resolve, reject){
fs.readdir(path, function(err, files){
if (err) return reject(err)
resolve(files)
})
})
}
function second (files) {
return Promise.all(files.map(function(value){
return makeQuery(value)
});
}
first()
.then(second)
.then(res.send)
.catch(errHandler)
Note that there is no done method on the Promise API.
One down side of the new Promise API is error handling. Take a look at bluebird.
It is a robust promise library which is compatible with the new promise API and has many of the Q helper functions.
As far as I can tell, you need to return a promise from getItemData. Use Q.defer() as you do in second(), and resolve it when the callbacks complete with the data. You can then push that into list.
To save code, you can use Q.nfcall to immediately call a node-style-callback function, and return a promise instead. See the example in the API docs: https://github.com/kriskowal/q/wiki/API-Reference#qnfcallfunc-args
I'm using the below code to get JSON from multiple urls. However, when one of the URL failed or get 404 response the function the execute doesn't work. I read the jquery doc and I know "then" should execute no matter one of the call has failed.
var data = {};
var calls = [];
for (var i in funcs) {
calls.push(
$.getJSON(base_url+i,
(function(i) {
return function(d) {
data[i] = d;
};
}(i))
)
);
}
$.when.apply($,calls).then(function() {
do_something(data);
});
Take a look at always method. It will executed in both cases.
For example:
$.when.apply($, calls).always(function() {
alert('Resolved or rejected');
});
In response to successful transaction, arguments are same as .done() (ie. a = data, b = jqXHR) and for failed transactions the arguments are same as .fail() (ie. a = jqXHR, b = errorThrown). (c)
I read the jquery doc and I know "then" should execute no matter one of the call has failed.
Nope, the promise only gets fulfilled if all of the passed objects are fulfilled. If one of them fails, the result will get rejected.
is there a way to make do_something(data); execute no matter if it failed or not.
You could use .always:
// doSomething waits until all are fulfilled or one is rejected
$.when.apply($,calls).always(do_something);
Yet, you probably want to execute the callback when all calls are resolved (no matter if fulfilled or rejected) - like allSettled does in Q. With jQuery, you have to work around a little:
var calls = $.map(funcs, function(_, i) {
var d = new $.Deferred;
$.getJSON(base_url+i).done(function(r/*…*/) {
d.resolve(i, r);
}, function(/*…*/) {
d.resolve();
});
return d.promise();
});
$.when.apply($, calls).then(function() {
var data = {};
for (var i=0; i<arguments.length; i++)
data[arguments[i][0]] = arguments[i][1];
do_something(data);
});
As jQuery docs the .then function takes two (or three) arguments in your case deferred.then( doneCallbacks, failCallbacks )
So you need to specify the second function to handle the failing request.
i.e.
$.when.apply($,calls).then(function() {
alerT('ok');
}, function() {
alert('fail');
});
Here a simple fiddle: http://jsfiddle.net/rrMwr/
Hope this helps
You need to use the second parameter of deferred.then() (documentation):
$.when.apply($, calls).then(function() {
do_something(data);
}, function() {
// something failed
});
If you want to call the same callback regardless of successes and failures, use deferred.always() (documentation):
$.when.apply($, calls).always(function() {
do_something(data);
});
It is also worth reading the jQuery.when() documentation, which explains the aggregation when multiple deferred objects are passed.