Effectively using $.when.apply(...) on custom promises - javascript

I'm using $.when.apply(methodArray[...]) in order to return the method callback arguments in my overall "done" callback. This works fine when the methods are $.ajax(options) calls, but sometimes the member methods are synchronous. I've tried every combination of $.Deferred, new Promise, $.when().then(...) that I can come up with, but I simply can't get the data from my sync call to show up as arguments in my done() callback. Here's an abbreviated example:
var loader() {
var obj = this;
this.execute = function (force, success, fail) {
if (!force) {
var data = {message: "This is cached data"};
if (data) {
//this does not return data to when.apply
return $.when().done(function () {
if (success) success(data);
return data;
});
}
}
return execute(success, fail); //this works fine
}
function execute(success, fail) {
return $.ajax({
type: obj.method,
url: obj.url,
data: obj.data,
dataType: obj.dataType || "json",
success: function (data) {
if (obj.cacheName) setCacheData(obj.cacheName, data);
if (obj.success) obj.success(data);
if (success) success(data);
},
fail: function (err) {
if (obj.fail) obj.fail(err);
if (fail) fail(err);
}
});
}
}
var selected = [] //array of loader objects with "execute(...)" command
$.when.apply($, $.map(selected, function (l) {
return l.execute(force, l.success, l.fail);
})).done(function () {
if (success) success($.map(arguments, function (a) {
return a[0]; //a is undefined on sync method
}));
}).fail(function () {
if (fail) fail($.map(arguments, function (a) {
return a[0];
}));
});
Basically, after the first (ajax) call, the data is cached. On subsequent calls, the "execute" function will just get the data from cache instead of the ajax call.
As mentioned - the when.apply code above works perfectly when the "execute" command is returning an $.ajax call. But when it is returning data that is cached (ie: from a previous ajax call) I cannot get the data passed back in the callback arguments.
How do I write this "execute" function to return EITHER the cached data or the $.ajax call as a promise - so that it is transparent (no difference) to the "when.apply"??
Another way I tried it:
if (data) {
var df = $.Deferred();
if (success) success(data);
df.resolve(data);
return df.promise(data);
}
That also did not work...

Related

How to handle this with async Ajax request and callback

So this function getXmlData() get's called across the app who's main responsibility is to return xml to and save it to a variable, in this example below it is 'test'
var test = getXmlData();
function getXmlData() {
queryData(getData);
}
function getData(xml) {
if (xml) {
return xml;
}
}
function queryData(callback){
$.ajax({
url: "/echo/JSON",
type: 'POST',
success: function(xml){
callback(xml);
},
error: function(){
console.log("Error!!");
}
})
}
Now we know that here parent function will return before the callback is executed and so 'test' variable will be undefined.
I'm not sure how can I handle this situation here. I'm tied to using this structure. I basically want getXmlData to return xml that is being returned in queryData. Suggestions!?

Using promises in javascript - only hitting the server once

I am trying to fully understand the usage of promises and the benefits they give. I have an AJAX call that grabs a bunch of data from the server. Right now I do not have promises implemented and the code hits the server anytime the user changes a view (all using the same data, just the way it looks).
Here is the promise I am trying to add:
function feedData(arr){
//data being initialized
this.initData();
}
feedData.prototype = {
constructor: feedData,
getData:function(){
return $.ajax({
url: 'php/getData.php',
dataType: 'json',
data: {
//data being sent over
}
});
},
initData:function(){
this.getData()
.done(function(result){
console.log(result.length);
})
.fail(function(x){
console.log(x);
});
},
....
}
I may not being fully understanding asyc behavior here. What I would have liked to do is get the result from getData and populate an object full of data that would be called whenever the user changes the view. From all I've read, thats not what promises are used for. Instead I should be returning a promise and using that data again? (Maybe this is my error of thought)
So my question is, once the data from getData is returned from AJAX, is there a way to return the promise and use the .done multiple times without hitting the server ever time? Meaning, since I will be using that same data and I can't save it to a global object, how could I achieve this?
Keep track of the promise returned by $.ajax(). This makes the call only once (in the constructor) regardless of how often you call getData():
function FeedData() {
this.data_promise = $.ajax({
url: 'php/getData.php',
dataType: 'json',
data: {}
});
}
FeedData.prototype = {
constructor: FeedData,
getData: function () {
return this.data_promise;
}
}
var feed = new FeedData();
feed.getData().then(function () {
/* .. */
});
You can also delay fetching until you call getData() for the first time:
function FeedData() {
this.data_promise = null;
}
FeedData.prototype = {
constructor: FeedData,
getData: function () {
if (this.data_promise === null) {
this.data_promise = $.ajax({
url: 'php/getData.php',
dataType: 'json',
data: {}
});
}
return this.data_promise;
}
}
Note, jQuery.ajax() returns a jQuery promise object.
At first successful $.ajax() call define a property to store the data at the instance. When .then() is called assign the result of $.ajax() to the value of the property at the object as a resolved Promise.
Retrieve the value from the object using instance.property.then().
function feedData(arr) {
var feed = this;
this.getData = function() {
return $.ajax({
url: 'php/getData.php',
dataType: 'json',
data: {
//data being sent over
},
// set `context` : `this` of `$.ajax()` to current `fedData` instance
context: feed
});
};
this.initData = function() {
// note `return`
return this.getData()
.then(function(result) {
console.log(result.length);
// define `this.promise` as a `Promise` having value `result`
this.promise = Promise.resolve(result);
return result;
})
.fail(function(x) {
console.log(x);
});
}
}
var request = new feedData();
request.initData().then(function(data) {
console.log(data)
});
// this will not make antoher request
request.promise.then(function(res) {
console.log("result:", res)
});
function feedData(arr) {
var feed = this;
this.getData = function() {
// do asynchronous stuff; e.g., `$.ajax()`
return $.Deferred(function(dfd) {
dfd.resolveWith(feed, [
[1, 2, 3]
])
});
};
this.initData = function() {
// note `return`
return this.getData()
.then(function(result) {
console.log(result.length);
// define `this.promise` as a `Promise` having value `result`
this.promise = Promise.resolve(result);
return result;
})
.fail(function(x) {
console.log(x);
});
}
}
var request = new feedData();
request.initData().then(function(data) {
console.log(data)
});
// this will not make another request
request.promise.then(function(res) {
console.log("result:", res)
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

JQuery AJAX - Filter before .done()

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

$q promise with foreach

I am writing an angular service to work with SharePoint data and I have run into a problem. I have a function in my service that updates and single item and returns an $http promise which works fine. The problem is I am trying to write a function now that utilizes the first function to loop and update multiple items. I want it to return a single promise once all items have been updated and it should reject if any of the items being updated failed. Here is the function:
this.UpdateListItems = function (webUrl, listName, itemsJson) {
if (numItems == -1) {
numItems = itemsJson.length;
c = 0;
f = 0;
}
var promises = [];
itemsJson.forEach(function (itemProps) {
var itemPromise = this.UpdateListItem(webUrl, listName, itemProps.Id, itemProps)
.then(function (response) {
c++;
if (c == numItems && f == 0) {
numItems = -1;
return itemsJson[listName];
}
}, function (error) {
c++; f++;
alert("ERROR!");//This gets called first alert below
if (c == numItems) {
numItems = -1;
return $q.reject(error);
}
});
promises.push(itemPromise.$promise)
}, this);
return $q.all(promises)
.then(function (data) {
alert("IN SUCCESS"); //This always gets called immediately after first item success instead of waiting for all items to finish
}, function (error) {
alert("IN ERROR"); //This never gets called
});
};
The $q.all is returning immediately after the first item returns successfully instead of waiting for the rest of the async item calls. Any help is much appreciated, I am new to all this. Thanks!
EDIT: Adding UpdateListItem code as requested:
this.UpdateListItem = function (webUrl, listName, itemId, itemProperties) {
if (typeof lists[listName] === 'undefined') {
lists[listName] = [];
}
var post = angular.copy(itemProperties);
DataUtilitySvc.ConvertDatesJson(post);
return this.GetListItemById(webUrl, listName, itemId)
.then(function (item) {
return $http({
url: item.__metadata.uri,
method: 'POST',
contentType: 'application/json',
processData: false,
headers: {
"Accept": "application/json;odata=verbose",
"X-HTTP-Method": "MERGE",
"If-Match": item.__metadata.etag
},
data: JSON.stringify(post),
dataType: "json",
}).then(function (response) {
var temp = [];
temp.push(itemProperties);
DataUtilitySvc.MergeByProperty(lists[listName], temp, 'Id');
return response;
}, function (error) {
return $q.reject(error);
});
}, function (error) {
return $q.reject(error);
});
};
Seems like this.UpdateListItem function already returned promise by having $promise object. That's why you were able to have .then(chain promise) function over it.
So basically you just need to push returned itemPromise object instead of having itemPromise.$promise inside promises array. Basically when you are doing $promise, it creates an array of [undefined, undefined, ...] and will resolve as soon as for loop completed.
Change to
promises.push(itemPromise)
from
promises.push(itemPromise.$promise)
Somewhat this question can relate to this answer

Returning jQuery Ajax Post

I wish to use the jQuery.post class to return (not alert) the response within a function.
The following gives an alert with the appropriate value:
function test_func() {
$.post("test.php", { cmd: "testing" }, function (data) { alert(data); })
}
(displays alert with appropriate value)
I tried the following:
function test_func() {
return $.post("test.php", { cmd: "testing" }, function (data) { return data; })
}
(returned object)
function test_func() {
var tmp;
$.post("test.php", { cmd: "testing" }, function (data) { tmp=data; })
return tmp;
}
(returned undefined)
var tmp;
function setTmp(n) {
tmp=n;
}
function test_func() {
t=$.post("test.php", { cmd: "testing" }, function (data) { setTmp(data); })
}
(returned undefined)
function test_func() {
t=$.post("test.php", { cmd: "testing" })
return t.responseText;
}
(returned undefined)
So what's the deal? How can I make "test_func()" return the data response text?
Being an asynchronous request, you aren't able to get a response as soon as you call the function. Instead, the function that you pass to $.post is intended to be a callback that will perform some action as soon as the response is complete. Consider the following:
function myCallback(response) {
// do something with `response`...
}
function test_func() {
$.post("test.php", { cmd: "testing" }, myCallback)
}
Instead of directly returning a response, you can instead manipulate it as needed in the myCallback function.
The deal is ajax is asynchronous one possible solution is to set it as sync like
$.ajaxSetup({
async:false
});
and then
function test_func() {
var temp;
t=$.post("test.php", { cmd: "testing" })
return t.responseText;
}
the answer is only to make your current setup work else there are better ways to deal with it

Categories