How to make http request in my case - javascript

I am trying to prevent multiple http requests being fired in my codes.
I have something like
in my controller
//if use click, fire this method
var getItems = function() {
factory.makeRequest.then(function(data){
//do stuff with data...
})
}
in my factory
var factory = {};
factory.makeRequest = function(){
if($http.pendingRequests.length===0) {
return getData()
.then(function(data) {
//do stuff here
return data;
})
}
}
factory.getData = function() {
return $http.get('/project/item');
}
return factory;
The above code will prevent the request being fired multiple time if the process is on going. However, I get Cannot read property of .then of 'undefined' if I click the button again before the promise is resolved. I know it's because the $http request doesn't return anything as I have $http.pendingRequests.length===0 condition. The above codes give me what I need but I don't know how to prevent the error in console log. Can anyone help me out? thanks a lot!

The following will return the current request promise, if it exists, otherwise it will create a new request and return that promise. It will clean up the current request on success.
var factory = {};
factory._currentRequest = null;
factory.makeRequest = function(){
if(!factory._currentRequest) {
factory._currentRequest = factory.getData()
.then(function(data) {
//do stuff here
factory._currentRequest = null;
return data;
})
}
return factory._currentRequest
}
factory.getData = function() {
return $http.get('/project/item');
}
return factory;

Related

Cannot read property 'then' of undefined with nested function calls

The code below is contrived. I'm simplified things as best I could to ask the question.
I have a simple angular service that makes an API call and returns results:
doWork = function(reqId) {
return $http.get('/api/dowork/' + reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
}
mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
}
In my controller, I can call the doWork function on the service and get back a good response like this:
myService.doWork('simple').then(function(response){
//do something great with response
});
However, if I need to call an intermediate method to preprocess the request, I get "Cannot read property 'then' of undefined":
myService.mediumRequest().then(function(response){
//do something great with response
});
Why doesn't the function mediumRequest return the promise that doWork returned to it?
Try this code , you did wrong in your service
var app = angular.module("myApp", [])
.service('myService',function($http,$q){
this.doWork = function(reqId) {
return $http.get('/api/dowork/'+ reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
};
this.mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
};
})
app.controller("myCtrl", function($scope,$compile,myService) {
myService.doWork('simple').then(function(response){
console.log('b',response)
});
myService.mediumRequest().then(function(response){
console.log('a',response)
});
})
It will work
As with most things, the problem was a coding error in my actual service code. The code I've presented in this question would work as expected. I appreciate the couple of folks who offered suggestions of ways to identify the issue. Here was my issue:
My actual "intermediate" function had this structure:
mediumRequest = function(options) {
//process first option meeting criteria
options.forEach(function (item) {
if(item.meetsCriteria)
{
var req = item.code;
return this.doWork(req);
}
});
}
As you can see, the return is actually just exiting the forEach and never actually being returned from the mediumRequest function. Hence the error.

Chaining Multiple Optional Async Ajax Requests

I'm using Angular 1.5.8. The views in my app require different combinations of the same 3 ajax requests. Some views require data from all three, others require data from two, or even one single endpoint.
I'm working on a function that will manage the retrieval of this data, requiring the app to only call each endpoint once. I want the ajax requests to be called as needed, but only when needed. Currently I've created a function which works, but seems like it could use improvement.
The following function is contained within the $rootScope. It uses the fetchData() function to cycle through the get requests as requested. When data is retrieved, it is stored in the global variable $rootScope.appData and then fetchData() is called again. When all data is retrieved the deferred promise is resolved and the data is returned to the controller.
$rootScope.appData = {};
$rootScope.loadAppData = function(fetch) {
var deferred = $q.defer();
function getUser() {
$http
.get('https://example.com/api/getUser')
.success(function(result){
$rootScope.appData.currentUser = result;
fetchData();
});
}
function getPricing() {
$http
.get('https://example.com/api/getPricing')
.success(function(result) {
$rootScope.appData.pricing = result;
fetchData();
});
}
function getBilling() {
$http
.get('https://example.com/api/getBilling')
.success(function(result) {
$rootScope.appData.billing = result;
fetchData();
});
}
function fetchData() {
if (fetch.user && !$rootScope.appData.currentUser) {
getUser();
} else if (fetch.pricing && !$rootScope.appData.pricing) {
getPricing();
} else if (fetch.billing && !$rootScope.appData.billing) {
getBilling();
} else {
deferred.resolve($rootScope.appData);
}
}
if ($rootScope.appData.currentUser && $rootScope.appData.pricing &&$rootScope.appData.billing) {
deferred.resolve($rootScope.appData);
} else {
fetchData();
}
return deferred.promise;
};
An object fetch is submitted as an attribute, this object shows which ajax requests to call. An example call to the $rootScope.loadAppData() where only user and pricing data would be requested would look like this:
$rootScope.loadAppData({user: true, pricing: true}).then(function(data){
//execute view logic.
});
I'm wondering:
Should the chaining of these functions be done differently? Is the fetchData() function sufficient, or is this an odd way to execute this functionality?
Is there a way to call all needed Ajax requests simultaneously, but wait for all required calls to complete before resolving the promise?
Is it unusual to store data like this in the $rootScope?
I'm aware that this function is not currently handling errors properly. This is functionality I will add before using this snippet, but isn't relevant to my question.
Instead of using the .success method, use the .then method and return data to its success handler:
function getUserPromise() {
var promise = $http
.get('https://example.com/api/getUser')
.then( function successHandler(result) {
//return data for chaining
return result.data;
});
return promise;
}
Use a service instead of $rootScope:
app.service("myService", function($q, $http) {
this.loadAppData = function(fetchOptions) {
//Create first promise
var promise = $q.when({});
//Chain from promise
var p2 = promise.then(function(appData) {
if (!fetchOptions.user) {
return appData;
} else {
var derivedPromise = getUserPromise()
.then(function(user) {
appData.user = user;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//chain from p2
var p3 = p2.then(function(appData) {
if (!fetchOptions.pricing) {
return appData;
} else {
var derivedPromise = getPricingPromise()
.then(function(pricing) {
appData.pricing = pricing;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//chain from p3
var p4 = p3.then(function(appData) {
if (!fetchOptions.billing) {
return appData;
} else {
var derivedPromise = getBillingPromise()
.then(function(user) {
appData.billing = billing;
//return data for chaining
return appData;
});
return derivedPromise;
);
});
//return final promise
return p4;
}
});
The above example creates a promise for an empty object. It then chains three operations. Each operations checks to see if a fetch is necessary. If needed a fetch is executed and the result is attached to the appData object; if no fetch is needed the appData object is passed to the next operation in the chain.
USAGE:
myService.loadAppData({user: true, pricing: true})
.then(function(appData){
//execute view logic.
}).catch(functon rejectHandler(errorResponse) {
console.log(errorResponse);
throw errorResponse;
});
If any of the fetch operations fail, subsequent operations in the chain will be skipped and the final reject handler will be called.
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs. -- AngularJS $q Service API Reference - Chaining Promises
Found a good way to answer question 2 in the original post. Using $q.all() allows the promises to execute simultaneously, resolving once they all complete, or failing as soon as one of them fails. I've added this logic into a service thanks to #georgeawg. Here's my re-write putting this code into a service, and running all calls at the same time:
services.factory('appData', function($http, $q) {
var appData = {};
var coreData = {};
appData.loadAppData = function(fetch) {
var deferred = $q.defer();
var getUser = $q.defer();
var getPricing = $q.defer();
var getBilling = $q.defer();
if (!fetch.user || coreData.currentUser) {
getUser.resolve();
} else {
$http
.get('https://example.com/api/getUser')
.success(function(result){
coreData.currentUser = result;
getUser.resolve();
}).error(function(reason) {
getUser.reject(reason);
});
}
if (!fetch.billing || coreData.billing) {
getBilling.resolve();
} else {
$http
.get('https://example.com/api/getBilling')
.success(function(result) {
coreData.billing = result;
getBilling.resolve();
}).error(function(reason) {
getBilling.reject(reason);
});
}
if (!fetch.pricing || coreData.pricing) {
getPricing.resolve();
} else {
$http
.get('https://example.com/api/getPricing')
.success(function(result) {
coreData.pricing = result;
getPricing.resolve();
}).error(function(reason) {
getPricing.reject(reason);
});
}
$q.all([getPricing.promise, getUser.promise, getBilling.promise]).then(function(result) {
deferred.resolve(coreData);
}, function(reason){
deferred.reject(reason);
});
return deferred.promise;
};
return appData;
});

How do you repeat an ajax call and be able to cancel it if needed in angular?

I have 3 ajax calls that need to be run serially. My issue is how do I do this with $interval. Is there another way to accomplish this?
I have these calls.
var server1 = getDataFromServer1()
var server2 = getDataFromServer2UsingServer1Data(server1);
var server3 = getDataFromServer3UsingServer2Data(server2);
However I want to repeat them but be able to cancel them if I get a certain value. Won't $interval run getDataFromServer1() before I get data from getDataFromServer3UsingServer2Data since it won't wait. Whats the best way to accomplish this? Thanks.
I am not sure whether this is correct approach or not but you can try something like this:
function getDataFromServer() {
getDataFromServer1().then(function(res1) {
if (value === YOU_CERTAIN_VALUE) {
//do what you want
return
}
getDataFromServer2UsingServer1Data(server1).then(function(res2) {
if (value === YOU_CERTAIN_VALUE) {
//do what you want
return
}
getDataFromServer3UsingServer2Data(server2).then(function(res3) {
if (value === YOU_CERTAIN_VALUE) {
//do what you want
return;
}
//calling the function again.
getDataFromServer();
});
});
});
}
This can be done using promise.
You should return a promise from each of your server call like below,
function getDataFromServer1(){
return $http.get('your_url', {options});
}
Here you are return the result from the $http. $http will return a promise so that you can handle this promise while calling this function.
Now you can achieve your requirement like this,
getDataFromServer1().then(function(response){
if(response.data == someValue){
// if you condition satisfied then call next server call
getDataFromServer2UsingServer1Data().then(function(response){
if(some_condition){
getDataFromServer3UsingServer2Data().then(function(response) {
});
}
});
}
});
Chaining promises is the better way to handle errors and elegant when there is such dependencies.
function getDataFromServer1() {
return $http
.get('/some/url/in/server1')
.then(function(response) {
$scope.server1Data = response.data; // If this data is needed in scope
return response.data;
});
}
function getDataFromServer2WithServer1Data(server1Data) {
if(server1Data.condition === 'not met') {
return $q.reject('server1 condition not met');
}
return $http
.get('/some/url/in/server2')
.then(function(response) {
$scope.server2Data = response.data; // If this data is needed in scope
if(server2Data.condition === 'not met') {
throw 'server2 condition not met'; // other way to abort the chain, it reaches the final catch block
}
return response.data;
});
}
function getDataFromServer3WithServer2Data(server2Data) {
return $http
.get('/some/url/in/server3')
.then(function(response) {
$scope.server3Data = response.data; // If this data is needed in scope
return response.data;
});
}
getDataFromServer1()
.then(getDataFromServer2WithServer1Data)
.then(getDataFromServer3WithServer2Data)
.catch(function(rejectReason){
console.log('error occured', rejectReason);
});
This blog explains more about this scenario, would recommend to go through it.

Bluebird Promising the result of a heavy function

I've been using Bluebird a lot recently on a HAPI API development. I've just run into my first real problem, that perhaps my understanding or naivety has me stumped.
The following code is an example of what I am facing:-
var Promise = require('bluebird'),
stuff = require('../stuff');
module.exports = {
getSomething: function(request, reply) {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p = p.then(function(resultFromPromise) {
//problems begin here
var data = stuff.doSomeReallyLongAndBoringFunction(resultFromPromise);
return data;
});
p.then(function(data) {
//no data here.
});
};
};
I've commented where the problems usually begin. the stuff.doSomeReallyLongAndBoringFunction() returns an object (using more promises concidently) and it's this object I want to access, but //no data here always fires before data returns. stuff.doSomeReallyLongAndBoringFunction() continues to run regardless and completes successfully, but after the code goes async, I don't know how to promise that function's return value back.
Can anyone offer any guidance? Please accept my apologies for any naivety in the question!
Help as always, is appreciated
NB just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself. Although, I did try return new Promise(reject, resolve) { }); manual wrap. It is simply a function that uses promises itself (successfully) to get data.
Update 1
stuff.doSomeReallyLongAndBoringFunction() is too big to post directly, but it does something like this:-
var Promise = require('bluebird'),
rp = require('request-promise');
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p.then(function() {
rp(options).then(function(response){
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
db.something.create({
Field: 'something'
}).exec(function(err, saved) {
return marshalledData;
});
});
}).catch(function(err) {
});
};
};
Update 2
Thank you Justin for your help. Here is the actual code, perhaps this may help?
Promise.resolve()
.then(function() {
if(typeof utils.intTryParse(place) !== 'number') {
return foursquare.createPlaceFromFoursquare(sso, place, request, reply);
} else {
return { Place: { PlaceId: place }};
}
}).then(function(placeObj) {
console.log('Place set as', placeObj); //always returns undefined, despite function actually completing after async op...
});
If your doSomeReallyLongAndBoringFunction is really running asynchronously, then it doesn't make sense to run it the way you have setup.
Edit - Here's a simple explanation of the way your code looks to be running vs a refactored version. It's been simplified , so you'll need to fill in the relevant sections with your actual implementation.
var Promise = require('bluebird');
function myAsync() {
setTimeout(function(){
return 'done sleeping';
}, 2000);
};
//The way your code is running
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return myAsync(); //your error is here
})
.then(function(done){
console.log(done);
});
//refactored
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return new Promise(function(resolve) {
setTimeout(function(){
resolve('done sleeping');
}, 2000);
});
})
.then(function(done){
console.log(done);
});
just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself.
And that's your problem. As it does something asynchronous and you want to get its result, it should return a promise. In fact, that's the case for every asynchronous function, especially then callbacks! It should be something like
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
return db.find()
// ^^^^^^
.then(function() {
return rp(options).then(function(response){
// ^^^^^^
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
return db.something.create({
// ^^^^^^
Field: 'something'
}).execAsyc();
});
}).catch(function(err) {
});
}
};
Your getSomething method has the same issues, and should look like this:
var createPlace = Promise.promisify(foursquare.createPlaceFromFoursquare);
module.exports = {
getSomething: function(request) {
var p;
if (typeof utils.intTryParse(place) !== 'number')
p = createPlace(sso, place, request); // this returns a promise!
else
p = Promise.resolve({Place: {PlaceId: place}});
return p.then(function(placeObj) {
// ^^^^^^
console.log('Place set as', placeObj);
});
}
};
See also these generic rules for promise development.
doSomeReallyLongAndBoringFunction needs to look like this:
doSomeReallyLongAndBoringFunction: function(param) {
var resolver = Promise.defer();
/*
* do some asynchronous task and when you are finished
* in the callback, do this:
*/
resolver.resolve(resultFromAsyncTask);
/*
*
*
*/
return resolver.promise;
}

angularjs promise then not called first time

This is a follow up question for Angularjs $http wait for response
Since i was unable to find a solution for that, i thought i will return a promise always and let my directive do the work in promise.then() function.
$scope.getVCard = function(id){
var vcardKey = vcardKeyPrefix+id;
var vCardFromLS = localStorageService.get(vCardKey);
if(vCardFromLS){
var deferred = $q.defer();
deferred.resolve({data:localStorageService.get(vCardKey)});
return deferred.promise;
}
}
and in my directive i am using it as
(function(angular, app) {
app.directive('popOver',["$window","$http",function($window,$http){
return function(scope,elem,attrs){
elem.on('mouseover',function(){
console.log('mouseover');
var promise = scope.$apply(attrs.popOver);
promise.then(function(data){
console.log('promise then called');
console.log(data);
//logic here
});
console.log('in directive again');
console.log(data);
});
};
}]);
})(angular, app);
But promise.then() is not getting invoked on first time. It gets invoked and works fine on subsequent mouse overs. What can be the issue?
I tried adding $scope.$apply() just before return deferred.promise but i am getting apply already in progress error. What am i missing here?
I believe it is because you are resolving it before returning it. I could be wrong though.
Try this:
$scope.getVCard = function(id){
var vcardKey = vcardKeyPrefix+id,
vCardFromLS = localStorageService.get(vCardKey),
deferred = $q.defer();
if(vCardFromLS){
$timeout(function(){
deferred.resolve({data:vCardFromLS});
}, 100);
} else {
$timeout(function(){
deferred.reject();
}, 100);
}
return deferred.promise;
}

Categories