I'm making multiple API calls, after which I want to load the combined results of each call:
$.when(
$.get(localAPI, data, function(response) {
globalStore.localShares = Number(response);
}),
$.get(facebookAPI, '', function(response){
globalStore.facebookShares = Number(response[0].share_count);
}),
$.getJSON(pinterestAPI, {url: url}).done(function(response){
globalStore.pinterestShares = Number(response.count);
})
).always(function(){
//Do stuff
});
If the $.get calls fail, the $.always callback function still executes.
But
If just one $.get call fails, it negates the actions of the previous calls.
So, if the first call fails, globalStore returns with two items. If the first call succeeds but the second fails, globalStore returns with only one item. And if the first two calls succeed but the last one fails, globalStore returns empty.
Is there any way around this?
Edit:
Yes, I have tried to handle fails within $.when like this:
$.when(
$.get(mu30_ajax_frontend.ajaxurl, data, function(response) {
globalStore.localShares = Number(response);
}).fail(function(){
globalStore.localShares = 0;
}),
$.get(facebookAPI, '', function(response){
globalStore.facebookShares = Number(response[0].share_count);
}).fail(function(){
globalStore.facebookShares = 0;
}),
$.getJSON(pinterestAPI, {url: url}).done(function(response){
globalStore.pinterestShares = Number(response.count);
}).fail(function(){
globalStore.pinterestShares = 0;
})
).always(function(){
//Do stuff
});
But I get the same result.
There's no way around this when using $.when that way, if one request fails, the entire chain fails.
You'd have to roll your own instead, using deferreds to know when the calls are all completed, and always successfully resolving etc.
var defs = [new $.Deferred(), new $.Deferred(), new $.Deferred()];
$.get(localAPI, data, function(response) {
globalStore.localShares = Number(response);
defs[0].resolve(true);
}).fail(defs[0].resolve);
$.get(facebookAPI, '', function(response){
globalStore.facebookShares = Number(response[0].share_count);
defs[1].resolve(true);
}).fail(defs[1].resolve);
$.getJSON(pinterestAPI, {url: url}).done(function(response){
globalStore.pinterestShares = Number(response.count);
defs[2].resolve(true);
}).fail(defs[2].resolve);
$.when.apply($, defs).then(function(result) {
// result is an array, any true value is a successful request "[true, true, true]"
});
written verbosely, this could be prettied up with some functions and loops etc.
I think the solution I needed was actually a simple one. I didn't care about success or failure, I just wanted to know when ajax was done. So:
$.get(mu30_ajax_frontend.ajaxurl, data, function(response) {
globalStore.localShares = Number(response);
});
$.get(facebookAPI, '', function(response){
globalStore.facebookShares = Number(response[0].share_count);
});
$.getJSON(pinterestAPI, {url: url}).done(function(response){
globalStore.pinterestShares = Number(response.count);
});
$(document).ajaxStop(function(){
//Do stuff
});
From the jQuery $.when() docs (emphasis mine):
In the case where multiple Deferred objects are passed to jQuery.when(), the method returns the Promise from a new "master" Deferred object that tracks the aggregate state of all the Deferreds it has been passed. The method will resolve its master Deferred as soon as all the Deferreds resolve, or reject the master Deferred as soon as one of the Deferreds is rejected.
So, yes jQuery.when will immediately fail if one of the passed promises is rejected/fails.
Related
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.
I have a loop with multiple ajax request.
each ajax is managed thru always callback.
each ajax is pushed in promises array.
finally $.when is used on always.
If all $.ajax got success the $.when.apply($, promises).always(...);
is called when all $.ajax calls succeed.
But for example on 3 $.ajax call the second fails, the $.when.apply($, promises).always(...); is fired only after the second and not when all 3 $.ajax calls suceed.
Any help?
Following the code
$(".upload_all").bind("click", function() {
var confirmed_count = 0;
var error_count = 0;
var promises = [];
to_uploads.each(function(index,val) {
var elem = $(this);
var promise = $.ajax({
url: "/upload/storeUploadedFile",
type: 'POST',
}).always(function(data_or_jqXHR, textStatus, jqXHR_or_errorThrown) {
if (textStatus === "success") {
// ..
} else {
// ..
}
});
promises.push(promise);
});
$.when.apply($, promises)
.always(function() {
console.log("Promises Always! ") // do other stuff
});
}
As #kevin-b mentions, the promise that $.when returns (pretty much the same as Promise.all) will be rejected if any of the promises passed to it is rejected. You probably want this, to inform the user that some uploads where not successful.
But $.ajax(..).always (the same as promise.finally) means 'execute this callback when this promise is either rejected or resolved'. You probably want instead to do something when all ajax call succeed, or do something else when any of them fails. Use Promise.all(promises).then(...).catch(...) for that.
$(".upload_all").bind("click", function() {
var confirmed_count = 0;
var error_count = 0;
var promises = to_uploads.map(function(index,val) { // using map instead of each here saves us a couple of steps
var elem = $(this);
return $.ajax({
url: "/upload/storeUploadedFile",
type: 'POST',
})
.then(function() { confirmed_count += 1; })
.catch(function() {
error_count += 1;
return Promise.reject(); // Return a rejected promise to avoid resolving the parent promise
});
});
// You can use the native API here which does not require doing the `apply` hack
Promise.all(promises)
.then(function() { alert('All uploads successful'); })
.catch(function() { alert(`Successful uploads: ${confirmed_count}. Failed uploads: ${error_count}`); }
}
Remember JQuery uses an alternative implementation of promises, but they still respond to the native API:
.then (.done in JQuery flavor): attach a callback to be run on success
.catch (.fail in JQuery flavor): attach a callback to be run on error. Unless a rejected promise is returned this will resolve the promise.
.finally (.always in JQuery flavor): attach a callback to be run after any other callbacks run, despite of the promise being rejected or resolved.
How to touch url one by one via javascript ajax or jquery from array? Because if you touch big process php in one touch will make timeout, so how process one by one?
Example
var myurls = [
"http://example.com/grape.php",
"http://example.com/apple.php",
"http://example.com/orange.php",
"http://example.com/banana.php"];
May be if grape.php is done and then next to apple, if apple is done and then next to orange.
And then if all process finished, show alert success.
You mean this?
var myurls = [
"http://example.com/grape.php",
"http://example.com/apple.php",
"http://example.com/orange.php",
"http://example.com/banana.php"],cnt=0;
function process(data) {
console.log(data);
}
function loadUrl() {
if (cnt>=myurls.length) return;
$.get(myurls[cnt++],function(data) {
process(data);
loadUrl();
});
}
$(function() {
loadUrl();
})
Based on your question and the discussion we had in your comments, I figured that you wanted to perform AJAX calls sequentially based on an array of URLs you have.
Solution 1: Use repeated, self-referencing $.ajax() requests
This is undoubtedly the easier solution. What we have here is that we keep track of the position of the array we are in, and stop making AJAX requests when the array has been iterated through.
$(function() {
// Array of URLs
var myurls = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/posts/4"
];
// Iterate through array, and keep a cursor on which item we are at
var urlCount = 0,
ajaxCall = function() {
if (urlCount < myurls.length) {
console.log('Making AJAX call to url: '+myurls[urlCount]);
$.ajax({
url: myurls[urlCount]
})
.done(function(returnedData) {
console.log(returnedData);
urlCount++;
ajaxCall();
});
}
};
ajaxCall();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Solution 2: Using .then() chaining set up using a for loop
I have adapted the code provided in this answer, for your use case, since the example can be quite difficult to understand for those unfamiliar with deferred objects and returned promises.
So the trick is the following:
Set up a master deferred object
Set up a general function that will make AJAX calls for you
Loop through the array, chaining them using .then()
Kick start the first AJAX call on the master deferred object
$(function() {
// Array of URLs
var myurls = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/posts/4"
];
// Set up chain of AJAX requests
var d = $.Deferred(),
_d = d,
ajaxRequest = function(ajaxUrl) {
// Log in browser console that AJAX call is being made
console.log('Making AJAX call to: ' + ajaxUrl);
// Return deferred object for .then() chaining
return $.ajax({
url: ajaxUrl
});
};
// We chain each AJAX call to the next one
for (var i in myurls) {
// Use IIFE so that reference to `i` is fixed
(function(j) {
// Update _d for chaining
_d = _d.then(function() {
// The _request is a defered object returned
// So we can chain deferred methods such as .done()
var _request = ajaxRequest(myurls[j]).done(function(ajaxData) {
// Just to show that data is being returned after each call
console.log(ajaxData);
});
// Return the deferred object for chaining
return _request;
});
})(i);
}
// Kick start sequential ajax call
d.resolve();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I'm using request js library to make HTTP requests to my API. My single API call looks like this:
var options = {
method: "post",
url: 'http//example.com',
json: true,
headers: headers,
body: {key: value}
}
request(options, callback);
However, I have array of options, which are needed to be called one after another and I need to break whole chain if one of them fails.
If last chain finishes, I need to output result to console.
I know that chaining callbacks could be fulfilled via promises, but all examples that I have found uses predefined amount of chained requests.
Is it possible?
A recursive function which calls itself in the request callback should work.
options = [{...}, {...}];
function doRequests(options){
request(options.shift(), function(){
if(error){
return "handle error";
}
if(options.length > 0){
doRequests(options);
}
});
}
The first thing I would do would be to use a request library that returned a promise. Assuming you have such a thing then you just chain the promises together.
First create a resolved promise:
var promise = new Promise.resolve();
The for each new object you want to request:
promise = promise.then(() => requestToPromise(options));
will chain another request onto the existing promise and will fire off a new request only when the previous one has completed.
If you have an array, you can have an index into that array, and have the callback kick off the next request when the previous one finishes. Roughly:
var index = 0;
var options = [/*...array of options objects...*/];
doRequest() {
request(options[index], function(err, result) {
// ...handle the result/error here. If there's no error, then:
if (++index < options.length) {
// Kick off the next request
doRequest();
}
});
}
While the above can be Promise-ified, since your requestmethod appears not to be, it would just complicate things.
You can instead use request-promise
and do the following
import request = require('request-promise');
var options = [/*...array of options objects...*/];
requests = [];
options.forEach(function(option){
requests.push(request(option));
}
Promise.all(requests).then(function(reponses){
//all requests are done.
});
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.