My application has a lot of AJAX calls, each of them return a JSON response. Instead of validating the data in each of the the .done() calls, I'm trying compact the code.
What we have so far
$.ajax({
url: 'test',
type: 'GET',
data: {
_token: token
},
dataFilter: function(jsonResponse) {
return isValidJson(jsonResponse);
}
}).done(function(jsonResponse) {
// do things
});
isValidJson(jsonResponse) {
try {
var parsedJson = $.parseJSON(jsonResponse);
if (parsedJson.error == 1) {
notificationController.handleNotification(parsedJson.message, 'error');
return false;
}
} catch (err) {
notificationController.handleNotification('A server-side error occured. Try refreshing if the problem persists.', 'error');
return false;
}
return jsonResponse; // Have to return the original data not true
}
The expected behavior is that if dataFilter returns false, it will trigger .fail(), if it returns true then it will continue to .done(). Instead, it just continues to .done() with the result of isValidJson().
Is there also a way to make .fail() do something standard like send a notification to the user without having to put it under every AJAX call?
Easiest way is to create a shorthand for $.ajax, by extending it.
Extending the AJAX call
jQuery.extend({
myAjax: function(params){
// Here we can modify the parameters and override them e.g. making 'error:' do something different
// If we want to add a default 'error:' callback
params.error = function() {
console.log('its failed');
};
// or you can specify data parse here
if (params.success && typeof params.success == 'function') {
var successCallback = params.success;
var ourCallback = function(responseJson) {
if (isValidJson(responseJson)) { // Validate the data
console.log('The json is valid');
successCallback(responseJson); // Continue to function
} else {
console.log('The json is not valid');
}
}
params.success = ourCallback;
}
return $.ajax(params);
}
});
Now everytime you want to make an AJAX call in your application, you DO NOT use $.ajax({}). Instead, you use $.myAjax({});
Example
$.myAjax({
url: 'domain.com',
type: 'GET',
success: function(data) {
// Do what you'd do normally, the data here is definitely JSON.
},
error: function(data) {}
});
And this special function will handle all errors same way, no need to write those validators every time.
Try to do it like this (Not tested):
var jxhr = $.ajax({
url: 'test',
type: 'GET',
data: {
_token: token
},
dataFilter: function(jsonResponse) {
if (!isValidJson(jsonResponse)) {
jxhr.abort();
}
return jsonResponse;
}
}).done(function(jsonResponse) {
// do things
});
By using this strategy - you are violating "separation of concern" strategy.
Ajax should resolve or reject according to its action. Not according if response is JSON or not.
A possible solution : ( sure there are also another solutions)
function GetSanitized(d) {
return d.then(function(a) {
if (a.indexOf('{') > -1) //check if json ( just for example)
return $.Deferred().resolve(JSON.parse(a)); //return object
else
return $.Deferred().reject(a); //reject
},
function() {
return $.Deferred().reject("ajax error"); //ajax failed
}
);
}
var ajax = $.Deferred();
GetSanitized(ajax) .then(function (a){alert(" Json p's value is "+a["p"]);},function (a){alert("Error"+a);});
ajax.resolve("{\"p\":2}"); //simulate ajax ok , valid json
//ajax.resolve("\"p\":2}"); //simulate ajax ok , invalid json
//ajax.reject("\"p\":2}"); //simulate ajax bad , valid json
http://jsbin.com/vozoqonuda/2/edit
Related
Struggling to return a AJAX Result Variable back to JavaScript
Note that the $.ajax call below is synchronous (async: false).
Ajax Call
function getState(callback) {
$.ajax({
url: 'getSearchState.php',
data: { "state": callback },
type: 'GET',
async: false,
success: function(result){
alert(result);
},
error: function(result) {
alert(result);
}
});
}
Ajax PHP
<?php
// Database Setup and Query
while ($row = $xxxxx->fetch(PDO::FETCH_ASSOC)) {
$StateVal = $row['State'];
}
return $StateVal;
?>
Javascript Calling the Function
var URL = District.trim();
var StateURL = getState(URL);
It gets the URL vairable from the function just fine, but doesnt return anything.
Any help would be great!
There are problems with that code both client-side and server-side.
Client-side:
Your getState is never returning anything, so it's no surprise that you don't see anything other than undefined for StateURL.
Don't use synchronous ajax. It makes for horrible UX. But if you really, really want to keep using it, here's how you would:
function getState(state) {
var result; // <=== Where we'll put our result
$.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET',
async: false,
success: function(data) {
// Remember the result;
result = data;
},
error: function() {
result = /*...whatever you want to use to signal an error */;
}
});
// Return the result
return result;
}
Note that I changed the name of the argument to state, since it's not a callback.
But again, don't use synchronous ajax. Instead, use a callback or promises.
Promise: $.ajax already returns a promise, so just return that directly:
function getState(state) {
var result; // <=== Where we'll put our result
$.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET',
async: false,
success: function(data) {
// Remember the result;
result = data;
},
error: function() {
result = /*...whatever you want to use to signal an error */;
}
});
// Return the result
return result;
}
Note that I changed the name of the argument to state, since it's not a callback.
But again, don't use synchronous ajax. Instead, use a callback or promises.
Promise:
function getState(state) {
return $.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET'
});
}
Usage:
getState(URL)
.done(function(StateURL) {
// Use it
})
.fail(function() {
// Failed
});
Callback:
function getState(state, callback) {
$.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET',
success: function(data) {
// Call the callbback with the result
callback(data);
},
error: function() {
// Call the callback with an error
callback(/*...whatever you want to use tosignal an error */);
}
});
}
Usage:
getState(URL, function(StateURL) {
// Use it, check for error
});
Server-side:
As RiggsFolly pointed out, you're returning a string from your PHP code. But that won't output it. To use it client-side, you need to output it (e.g., echo and similar). And to make it easily consumed by the JavaScript, you probably want to json_encode it to ensure that it's in a format JavaScript can understand:
echo json_encode($stateVal);
Then in your success (or done) function, use JSON.parse on it:
result = JSON.parse(data);
this is jQuery and in this case you can specify context and in success function set variables on that context.... a bit crude solution but it will works. Also take a look on arrow functions and promises from ES6, it can help you a lot and give you new perspective about whole problem.
And one main thing!! Ajax is async by default so you need somehow notify your StateURL when data will be ready (here again promise at you service)
I'm currently looping through a number of functions that validate particular input values. One in particular requires an ajax call to validate an address. I saw that users here on SO suggested using callbacks to return the value.
The only problem is, by the time it does retrieve the value, the function had already fired within the loop and returned an undefined value. I've been searching around and wasn't sure what the best solution would be. Do I somehow delay the loop? Do I have my function setup right in the first place? Need some serious help.
var validations = [validateUsername, validatePassword, validateAddress];
function submitForm() {
var inputs = validations.map(function(validation) {
return validation();
});
return inputs.every(function(input) {
return input === true;
}); // [true, true, undefined]
}
function validateAddress() {
function addressIsValid(callback) {
$.ajax({
type: 'POST',
url: '/address/validate',
data: $form.serialize(),
dataType: 'json',
success: function(response) {
return callback(response.Data === 200);
}
});
}
function callback(response) {
return response;
}
return addressIsValid(callback);
}
You should use Promises.
First, you should make your asynchronous functions return a Promise:
function validateAddress() {
return new Promise((resolve, reject)=> {
$.ajax({
type: 'POST',
url: '/address/validate',
data: $form.serialize(),
dataType: 'json',
success: response=> {
response.Data === 200 ? resolve() : reject();
}
});
});
}
Then rewrite your submitForm function like this:
function submitForm() {
var promises = validations.map(validation=> validation());
return Promise.all(promises)
}
Then you can use submitForm like this:
submitForm()
.then(()=> {
// form is valid
}, ()=> {
// form is invalid
})
What I am trying to do:
1. Initially gives an ajax request to the server based on some inputs
2. The server returns a job id generated by RQ (Python-rq)
3. Based on the job id ajax request made to a url constructed with the jobid regularly till a valid response is obtained
What I have:
$.ajax({
type: "POST",
url: "/start",
data:{crop: valueCrop, state: valueState, variablemeasure: valueVariable, unit:unitMeasure, from:yearFrom, to:yearTo},
success: function(results) {
console.log(results);
var jobId='';
jobId = results;
function ajax_request() {
$.ajax({
type: "GET",
url: "/results/" + jobId,
dataType: "json",
success:function(xhr_data) {
if (xhr_data == {"status":"pending","data":[]}){
console.log("Waiting for response");
setTimeout(function() { ajax_request(); }, 2000);
} else {
console.log(xhr_data);
}
},
error:function(error) {
console.log(error)
}
});
}
},
error: function(error) {
console.log(error)
}
})
Is this even possible? I am not getting any output at all on the console although the rq says the job is finished. I think it is not entering that if loop. When I visit the "/results/jobId" url I am able to see the result.
Please help.
I see a few bugs in this code. First of all, you have defined the function ajax_request(). But you are not calling it. You can call it at the end of its definition.
Secondly, this code is problematic:
if (xhr_data == {"status":"pending","data":[]})
The object notation creates another object which is definitely not equal to xhr_data.
You can do:
if (xhr_data.status === "pending")
I have a service that calls a URL for fetching details of a user.
...
this.getUserDetail = function (userId) {
// deal cache
var request = $http({
method: "get",
url: "/users/"+userId
});
return request.then(successFn, errorFn);
};
But at another place, I am fetching the user details too and creating a map of newly fetched users. I want to reuse an already fetched user from my JavaScript object.
this.getUserDetail = function (userId) {
if (userMap[userId]) {
return $q.resolve({
'result': userMap['userId']
});
}
var request = $http({
method: "get",
url: "/users/"+userId
});
return request.then(successFn, errorFn);
};
But this doesn't work. I have included $q. I don't get JavaScript errors, except that at a place where I am using this.getUserDetail(userId).then(..., it throws error, as I am may be not returning a succesFn from the way I am doing it.
Am I doing it properly?
The function that you call is using AJAX.
Now from your question, since you are using then, this.getUserDetail(userId).then(), it means getUserDetail must return a promise itself.
Now if I understand it correctly, you want to resolve a promise with random data, without making AJAX call when an item is cached.
In that case, make your function to conditionally use promise object.
this.getUserDetail = function (userId) {
var cachedUser = userMap(userId),
deferredData = $q.defer();
var request = cachedUser ? deferredData.promise : $http({
method: "get",
url: "/users/" + userId
});
if (cachedUser) {
deferredData.resolve({
'data': {
'result': cachedUser
}
});
}
return request.then(successFn, errorFn);
};
Edit:
And then use it in your controller as:
this.getUserDetail.then(function(response){
// this response object is same object with which
// promise was resolved.
// Doesn't matter whether the promise was AJAX or your own deferred.
});
Doesn't matter whether the promise was AJAX or your own deferred.
You can use AngularJs built in cache:
var request = $http({
method: "get",
url: "/users/"+userId,
cache: true
});
this.getUserDetail = function (userId) {
if (userMap[userId]) {
return $q.when(userMap[userId]);
} else {
return $http({
method: "get",
url: "/users/"+userId
})
.then(function (result) {
// add to userMap;
return result;
});
}
};
I have a set of custom user data that I want to make an ajax call to, and in the event that there is no user data, make another ajax call to retrieve a default set of data, and then execute a function after parsing the data. Here's an example:
var oData = [],
exampleUrl = 'example.php';
$.ajax({
url: exampleUrl + '?query=getUserData',
contentType: 'application/json;odata=verbose',
headers: {
'accept': 'application/json;odata=verbose'
},
success : function(data, request){
// Request succeeded
// Check the results
if(data.length){
// There are custom user results!
// Parse the results
oData = data;
}
else{
// There were no custom user results...
// Run another query to retrieve default values
$.ajax({
url: examplUrl + '?query=getDefaultData',
contentType: 'application/json;odata=verbose',
headers: {
'accept': 'application/json;odata=verbose'
},
success : function(data, request){
// Request succeeded
// Check the results
if(data.length){
// There was some default data!
// Parse the results
oData = data;
}
else{
// No data was found...
// Attempt to be helpful
console.log('No Default data was found!');
}
},
error : function(data, request){
// There was an error with the request
// Attempt to be helpful
console.log('Error retrieving data:');
console.log(data);
console.log(request);
}
});
}
},
error : function(data, request){
// There was an error with the request
// Attempt to be helpful
console.log('Error retrieving Custom User data:');
console.log(data);
console.log(request);
},
complete : function(){
// Do something with the data
index.displayData(oData);
}
});
The issue is that if the second ajax call is run, oData doesn't contain any data at all when it's passed to index.displayData(). I'm guessing it has something to do with the asyncronous nature of ajax calls, but shouldn't 'complete' run after everything inside of 'success' runs?
I also know I probably shouldn't be using the ajax "Pyramid of Doom" and should be using promises, but I've tried them and keep getting the same results.
Thank you for your assistance!
As pointed out by Violent Crayon, you could try calling "complete" yourself instead of relying on JQuery's implicit control flow:
function getData(exampleUrl, onComplete){
$.ajax({
success : function(data, request){
if(data.length){
onConplete(data);
}else{
$.ajax({
success : function(data, request){
if(data.length){
onComplete(data);
}else{
console.log('No Default data was found!');
}
},
error : function(data, request){
console.log('Error retrieving data:');
}
});
}
},
error : function(data, request){
console.log('Error retrieving Custom User data:');
}
});
}
var oData = [];
getData('example.php', function(data){
oData = data;
index.displayData(oData);
}
BTW, note how you can have your async functions receive their own return and error callbacks. This can help reduce the pyramid of doom problem without needing to use promises and without needing to hardcode the return callback.
By working with promises, you can avoid the need to pass a callback into your function, and by defining a utility function you can avoid repetition of code.
//reusable utility function, which returns either a resolved or a rejected promise
function fetchData(queryString, cache) {
return $.ajax({
url: 'example.php',
data: { query: queryString },
type: 'JSON',//assumed
cache: cache,
contentType: 'application/json;odata=verbose',
headers: { 'accept': 'application/json;odata=verbose' }
}).then(function(data, textStatus, jqXHR) {
if (data && data.length) {
return data;
} else {
return $.Deferred().reject(jqXHR, 'no data returned').promise();//emulate a jQuery ajax failure
}
});
}
This allows promise methods to be used for a control structure, which :
is concise
uses chaining, not nesting
gives meaningful error messages.
//control structure
fetchData('getUserData', false).then(null, function(jqXHR, textStatus) {
console.log('Error retrieving Custom User data: ' + textStatus);
return fetchData('getDefaultData', true);
}).then(index.displayData, function(jqXHR, textStatus) {
console.log('Error retrieving default data: ' + textStatus);
});
Notes :
the null in .then(null, function(){...}) allows a successful response to drop straight through to the second .then(index.displayData, ...)
default data is cached while the user data is not. This is not necessary to make things work but will be faster next time the default data is required.
in the world of promises, this or something similar is the way to go.