Cancel pending API calls in Restangular - javascript

I have API service:
var SearchSuggestionApi = function (Restangular) {
return {
getSuggestion: function (keyword) {
return Restangular.one('search').customGET(null, {keyword:keyword});
}
};
};
SearchSuggestionApi.$inject = [
'Restangular'
];
I have controller to call this API:
vm.getSuggestion = function (keyword) {
SearchSuggestionApi.getSuggestion(keyword)
.then(
function (data) {
vm.listData = data;
}, function (error) {
console.log(error);
});
};
My problem is when I call vm.getSuggestion(keyword) two or many time (must call than one time). Such as:
vm.getSuggestion('a'); // => Return a array with many object in this
vm.getSuggestion('a b');// => Return empty array
Because vm.getSuggestion('a') return many data, it will finish after vm.getSuggestion('a b'). So vm.listData is [{object1}, {object2}, ...], but I want to vm.listData is [] (response data of the last function).
How can to cancel pending API call in first function when I call seconds function or another ways to get the last response data and set for vm.listData.
I researched some articles about cancel pending API calls, but it not help me about my problem.
Thanks for your help :)

There are various ways of solving this:
You can simply check in your then callback whether the value received is still current:
vm.getSuggestion = function (keyword) {
SearchSuggestionApi.getSuggestion(keyword)
.then(
function (data) {
if (vm.keyword === keyword) {
vm.listData = data;
}
}, function (error) {
console.log(error);
});
};
You can cancel the request by specifying a timeout promise
If you are solving this problem often, you might wish to replace the promise by an RxJS observable stream with the appropriate operators. This is the cleanest solution, but does require an additional library.

Related

Can't loop through an array of objects in Typescript (Angular)

in my project I'm fetching data from an API (trip entities). This is my model:
//Trip.model.ts
export class Trip{
created_at?:Date;
updated_at?:Date;
.... bunch of fields
}
In my component I'm fetching the data and assigning it to the trips variable. However, when I'm trying to access any of the items in the trips array I get 'undefined'. I also can't loop through it, I tried both forEach and for...in/of.
I tried using an interface instead of a class but with no luck. How can I loop through that array of objects in order to use the data in it?
Component's code:
userName:string='';
trips:Trip[]=[];
moment:any=moment;
usersData:any={};
constructor(private auth: AuthService,
private storage: LocalStorageService,
private translate: TranslateService,
private tripService: TripService){}
ngOnInit(): void {
console.log(this.translate.currentLang)
this.userName=localStorage.getItem('username')!;
this.fetchTrips();
this.fetchPics();
}
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
}, error: error => {
console.log(error);
}
});
}
//fetchPics because I want to extract
//user's profile pics' urls from the trips array
fetchPics(){
console.log(this.trips);
console.log(this.trips[0]);
this.trips.forEach((trip)=>{
console.log(trip);
});
}
getTrips service method:
getTrips(){
return this.http.get<any>(Api.API+Endpoints.TRIP);
}
This is what shows when I
console.log(this.trips)
after assignment.
Data from the API:
Pictures cropped to make them more readable.
You are trying to get access to this.trips value before you actually have your data on it.This happens becouse you get that data asynchronously, just inside the subscribe of this.tripService.getTrips()
So, in order to solve your problem, you just need to move this invoke:
this.fetchPics();
inside the subscribe of fetchTrips() method, like this:
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
this.fetchPics();
});
}, error: error => {
console.log(error);
}
});
}
The function fetchPics() executes before your getTrips call ends. You need to call the method only after your HTTP call ends, and after you populate your trips array successfully.
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
//Populate your trips array
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
// this is where you need to call
this.fetchPics();
}, error: error => {
console.log(error);
}
});
}
This is happening because of JS is asynchronous. you are making an http request here, that may take some time to get data from server. let's assume that might take 1 minute untill then compiler will not stop it's execution process In that 1 min of time it will execute next statements.because of that your fetchpics() method is being executed before fecthtrips() execution done. To overcome this we can use async, await as like below.
async fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => await {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
}, error: error => {
console.log(error);
}
});
}

debounceTime() over API calls using Rxjs

I am trying to understand rxjs and got stuck at a debounceTime(n /* ms */) experiment.
public debounceTime(dueTime: number, scheduler: Scheduler): Observable
Emits a value from the source Observable only after a particular time span has passed without another source emission.
source
My code:
function fakeAPI() {
return new Rx.Observable(observer => {
const root = 'https://jsonplaceholder.typicode.com'
$.ajax({
url: root + '/posts/1',
method: 'GET'
}).then(function(data) {
observer.next(data)
}).fail(function(err) {
observer.error(err)
})
return ()=>{
observer.complete()
console.log('unsubscribed!')
}
})
}
const fakeObserver = fakeAPI()
$('#buttonText').click(()=>{
fakeObserver
.debounceTime(10000)
.subscribe(() => {
return {
next(item) {
console.log('received: ', item.id)
},
error(err) {
console.log('error:', err)
},
complete() {
console.log('completed!')
}
}
}());
})
My expectation: Even with N number of clicks in the given amount of time, the API call would only be made once. Instead, it seems like it waits for the given time and then all the N clicks result in an API call.
What am I doing wrong?
As per the docs, debounceTime(n) is supposed to discard previous pending delayed emissions if a new value arrives on the source.
Here is a JSBin link
As per the docs, debounceTime(n) is supposed to discard previous pending delayed emissions if a new value arrives on the source.
It's true but on each click:
You create new subscription
It calls api
Api returns result
debounceTime waits 10s (nothing happens because observerable returned by fakeObserver emits only once)
You log the result
You need to convert your clicks in observable to implement what you want:
Rx.Observable.fromEvent(document.getElementById('buttonText'), 'click')
Check jsBin

Angular "Cannot read property 'then' of undefined" with promise

I am stuck on why my promises aren't working. From the other articles I have looked at, I think I am doing it correctly. Here is the code I have currently:
Factory code
factory.isLoggedIn = function() {
$http.get('/api/v1/fitbit/auth')
.success((data) => {
return data.status;
})
.error((error) => {
console.log('Error: ' + error);
});
}
Controller code
$scope.fitbitAuth = function() {
FitbitFactory.isLoggedIn()
.then(
function(data) {
$scope.fitbitStatus = data;
},
function(errorData) {
console.log(errorData);
});
return $scope.fitbitStatus;
};
From my understanding of promises, the return $scope.fitbitStatus should be populating the $scope.fitbitAuth, but it isn't. I am also returning a boolean in the Factory, which should be populating $scope.fitbitStatus.
You have to return something (the promise), or it is undefined.
Factory code:
factory.isLoggedIn = function() {
return $http.get('/api/v1/fitbit/auth');
}
Controller code:
$scope.fitbitAuth = function() {
return FitbitFactory.isLoggedIn()
.then(
function(data) {
$scope.fitbitStatus = data;
},
function(errorData) {
console.log(errorData);
});
};
The complete success/error block in your factory is not necessary and should be removed. I'm also unsure why you return $scope.fitbitStatus; as it is undefined at the time of return.
Edit: Edited the answer to actually return the promise.
Currently you haven't return anything from isLoggedIn factory method and you are calling .then method over it.
To make it working return $http promiseobject from service method. In your case you could simply return$http.getmethod call which return promise object itself and then you can easily chain them up by callingFitbitFactory.isLoggedIn().then`
factory.isLoggedIn = function() {
return $http.get('/api/v1/fitbit/auth');
}

Race condition in Angular HTTP promises

So, i have a small API interaction code that looks like this:
function load_posts() {
return $http
.get('/posts')
.then(on_success);
function on_success(response) {
return response.data;
}
}
function get_posts() {
if (blog.posts) {
return $q.when(blog.posts);
}
return load_posts().then(function (posts) {
blog.posts = posts;
return blog.posts;
});
}
I do this to avoid hitting the API for the same results all the time. I have several separate directives and components that might need to call this API endpoint, but they don't need a fresh result everytime. But this results in an ugly race condition: if two or more components call the get_posts method before the load_posts response arrives, then they all issue API requests. There are no side-effects, because this is just a cache attempt, but it defeats the whole purpose.
Any ideas on how to proceed with this one?
The $http service can cache requests. See here or the docs for a deeper explanation of how the caching works.
The default $http cache can be particularly useful when our data
doesn’t change very often. We can set it like so:
$http({
method: 'GET',
url: '/api/users.json',
cache: true
});
// Or, using the .get helper
$http.get('/api/users.json', {
cache: true
});
Now, every request that is made through $http to the URL
/api/users.json will be stored in the default $http cache. The key for
this request in the $http cache is the full-path URL.
This isn't really a race-condition problem, it's just a matter of memoizing the function. You can use something like memoize() from Underscore.js or just implement it yourself:
var load_posts = () => {
const p = $http
.get('/posts')
.then(response => response.data);
load_posts = () => p;
return p;
};
1) Extract data retrieval into separate "blogService" service;
2) Cache promise in your service that does the request;
3) Return the same promise for all clients, you can manipulate results if you dont want to expose whole response object;
var promise = null;
function loadBlogs() {
promise = promise || $http.get("/posts").then(function(response){return reponse.data;});
return promise;
}
4) Then just call service method and wait for promise to resolve wherever you need (controller, directive, etc):
function getPosts() {
blogService.loadBlogs().then(function (posts) {
vm.posts = posts;
});

angular controller is executing before factory complete

I have moved some common code to factory. but the controller is executing before factory get loaded. In this case i am getting the blank response(zero results)
can anyone suggest the best solution.
here is my angular factory,
app.factory('TabsFactory', function($resource){
var activetabs = {};
activetabs.getDepositAccountDetails = function() {
return $resource('xxxx/:number', {}, {
getDepositAccountDetailsService: {
method: 'GET',
isArray: false
}
});
}
activetabs.getAccountInfo = function(){
return accountinit.accountInfo;
}
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
return activetabs;
});
controller,
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo);
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
You should use chain promise to update scope variable, because your accountInfo variable is updated inside $resource promise.
Code
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo).then(function(data){
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
});
Update
Service method should return promise inorder to continue promise chain
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
//added return below
return activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
return accountinit.accountInfo;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
Yes, this will happen because of JavaScript executing asynchronous operations but your controller in such a way that it expects things to be synchronous operations.
When you call TabsFactory.getAccountInfo() its possible that your $resource('xxxx/:number') is still not completed and response ready for you to process!!
So, what to do? You have make use of promise. I usually have a repository (A factory with method that return promise) to handle server communications. Here is an example:
app.factory('accountRepository', ["$http","$q",function($http,$q){
return {
getDepositAccountDetails : function(id) {
var deferred = $q.defer();
$http.ger('xxx').success(deferred.resolve).error(deferred.reject);
return deferred.promise;
}
};
}] );
My repository will have more operations like add account, update account info etc..
my controller/service then calls these methods as follows:
accountRepository.getDepositAccountDetails(123).then(function(response) {
// Process the response..
}, function(error) {
// Some error occured! handle it
});
doing so, my code gets executed only after I get response from server and data is ready for consumption or display. Hope this helps..
Update: You might want to have a look at this to get the idea ;)

Categories

Resources