Using promises in javascript - only hitting the server once - javascript

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>

Related

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

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...

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!?

Where to write Success and Error

Here I'm using Angularjs1.x and here is my condition. If condition is success then show the table otherwise throw an error. I know some code if its Success.
AngCtrl.Js
$scope.BtnCall = function () {
ServiceCntrl.CallData().then(function (d) {
$scope.EmpData = d.data;
});
}
AngService.Js
eApp.service("ServiceCntrl", function ($http) {
var xx = '';
xx= $http({
data: formData,
method: 'post',
url: Url,
datatype: "json"
}).success(function (rsp) {
RspData = rsp;
return RspData;
}).error(function (rsp) {
console.log('Error');
});
return xx;
};
Your x.then receives two functions x.then(function(){}, function(){}); first function is called when promise is successfully resolved and second function is called if promise is rejected(failed).
If your service function is return $http promise then your first function can have a parameter named(anything you like) and it will have response data that you can use. Second function can receive error parameters if any.
you should look at angular $http service documentation.
If your service is returning the promise of the get request, then you can write
$scope.BtnCall = function () {
var x = ServiceCntrl.CallData();
x.then(function(response) {
//Success callback
//code on success goes here
//response.data
}, function(response) {
//error callback
//code on error goes here
// server returns response with an error status.
});
you can use the ng-show/ng-hide to show and hide the contents on the html page.
You can write your success/fail code as the following:
$scope.BtnCall = function() {
var x = ServiceCntrl.CallData();
x.then(function(result) {
// Success code here
// Do something and resolved the promise
}, function(reason) {
// Error code here
// You may handle/reject the reason in params
});
});
See also the Angular docs for $q.
The AngularJS $http service makes a request to the server, and returns a response
The example above executes the $http service with an object as an argument. The object is specifying the HTTP method, the url, what to do on success, and what to do on failure.
$scope.BtnCall = function () {
ServiceCntrl.CallData().then(function (d) {
$scope.EmpData = d.data;
});
}
AngService.Js :
eApp.service("ServiceCntrl", function ($http) {
var xx = '';
xx= $http({
data: formData,
method: 'post',
url: Url,
datatype: "json"
}).success(function (rsp) {
RspData = rsp;
return RspData;
}).error(function (rsp) {
console.log('Error');
});
return xx;
};

Load data using Callback. Is there a better solution using ES6 Promises?

I have a method that is called in several places in the project. I've done method. the first method call do Ajax get, cache data in class property and fire callback. Second call method only call callback with cached data. I would like to add the ability to load data synchronously. Date should be returned by the method. I added an additional parameter to call {async: false}, but I wonder if there is a better solution using ES7 promises?
This is my callback solutions.
export class loadData {
constructor() {
this.data = [];
}
getData({callback, async = true}){
let syncData = this.data;
if( this.data.length === 0 ){
$.ajax({
beforeSend: authorizationManager.addAuthorizeHeader(),
url: apiUrl + '/Data/datadata',
dataType: 'json',
cache: true,
async: async
}).done((data)=>{
if(async) callback(data);
this.data = data;
syncData = data;
});
} else {
if(async) callback(this.data);
}
if(async === false) return syncData;
}
}
loadDataTest = new loadData();
call async
loadDataTest.getData({
callback: (data) =>{
console.log(data);
}
});
call sync
let a = loadDataTest.getData({
async: false
});
Promises are almost always the better solution. Of course they are never synchronous, but that's usually the better solution as well. This is how it would look like:
export class loadData {
constructor() {
this.promise = null;
}
getData() {
if (this.promise == null) {
this.promise = Promise.resolve($.ajax({
beforeSend: authorizationManager.addAuthorizeHeader(),
url: apiUrl + '/Data/datadata',
dataType: 'json',
cache: true
}));
}
return this.promise;
}
}
And the call:
loadDataTest.getData().then((data) => {
console.log(data);
});
I would like to add the ability to load data synchronously
I don't think you really want that. If all you want is synchronous-looking syntax for asynchronous functionality, have a look at async/await.

How can I force this AngularJS service to wait until it has a value to return?

I have a service that is called by multiple controllers. It loads data into an object categories:
.service('DataService', ['$http', '$q', function($http, $q) {
var categories = {};
// Return public API.
return({
setCategory: setCategory,
getCategory: getCategory,
getJSON: getJSON,
categories: categories
});
function setCategory(name, category) {
console.log("setting category");
console.log(name, category)
categories[name] = category;
}
function getCategory(name) {
console.log("getCategories:");
console.log(categories[name]);
return categories[name];
}
function getJSON() {
//JSON stuff, where categories gets its initial values.
}
I call getCategory(name) in many places, and in some instances, it is called before categories has populated, e.g:
$scope.category = DataService.getCategory(name);
//$scope.category is undefined
How can I build this Service so that getCategories waits until categories is defined before returning its value? Alternately, how can I write the Controller so that getCategories isn't defined until categories has a value? I have tried using a $scope.$watch function in the controller to watch DataService.categories, to no success- it never logs an updated value.
Use the promises you're already injecting in your service. Here is just one of the many possible ways you can do this:
var pendingQueue = [];
var loaded = false;
var self = this;
function getCategory(name) {
var deferred = $q.defer();
if (loaded) {
// Resolve immediately
console.log('Already loaded categories, resolving immediately...');
deferred.resolve(self.categories[name]);
return deferred.promise;
}
// Queue the request
pendingQueue.push({
promise: deferred.promise,
name: name
});
if (pendingQueue.length === 1) {
console.log('First request for a category, requesting...
// We are the FIRST request. Call whatever it takes to load the data.
// In a 'real' language this wouldn't be thread-safe, but we only have one, so...
$http.get('/my-data').then(function(data) {
self.categories = data;
console.log('Loaded categories', self.categories);
loaded = true;
pendingQueue.map(function(entry) {
entry.promise.resolve(entry.name);
});
pendingQueue.length = 0;
});
}
return deferred.promise;
}
Then in your controller:
DataService.getCategory(name).then(function(category) {
// Do something with category here
});
This will:
For the first request, make the async request and then resolve the promise once the data is obtained.
For the second - Nth request BEFORE the data is obtained, queue those without making duplicate requests.
For requests AFTER the data is obtained, resolve immediately with the requested data.
No error handling is done - you should use deferred.reject() to send those back to the caller, and .catch() / .finally() to handle them in the controller(s).
There are many solutions - this is just one option.
Inside DataService
yourApp.service('DataService', function($resource, $q) {
var resource = $resource('/api/category/:id', {}, {
query: {
method: 'GET',
isArray: false,
cache: false
},
save: {
method: 'POST',
isArray: false,
cache: false
}
});
return {
getCategory: function(id) {
var deferred = $q.defer();
resource.query({id: id},
function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
}
);
return deferred.promise;
},
setCategory: function(categoryObj) {
var deferred = $q.defer();
resource.save(categoryObj,
function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
}
);
return deferred.promise;
},
getJSON: function() {
// stuff to do
}
};
});
Inside DataController:
yourApp.controller('DataCtrl', function($scope, DataService) {
$scope.handleSuccessResponse = function(response) {
$scope.data = response;
};
$scope.handleErrorResponse = function(response) {
$scope.error = response;
};
DataService.getCategory(123).then($scope.handleSuccessResponse, $scope.handleErrorResponse);
});

Categories