Race condition in Angular HTTP promises - javascript

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;
});

Related

How to manage asynchronous properly

my code outputs everytime different numbers. Is this a proper way I am using it?
Here is the code:
export class GetPlanetsService {
url='https://swapi.co/api/planets/?page=';
planets:Planet[]=[];
headers: HttpHeaders = new HttpHeaders()
.set('Accept', 'application/json');
constructor(private http:HttpClient) { }
getPlanet(pageIndex){
return this.http.get<Planets>(`${this.url}${pageIndex}`,{headers:this.headers});
}
getAllPlanets(){
let numberOfPages=7; // Tried to do it dynamically but got infinite loop
for(let j=1;j<=numberOfPages;j++){
this.getPlanet(j).subscribe(value=>{
for(let i=0;i<value.results.length;i++){
this.planets.push(value.results[i]);
if(j==numberOfPages && i==(value.results.length-1)){
console.log(this.planets); //There is outputted everytime different number
}
}
});
}
}
Have you got any tips and could you explain it in simple words?
Regards
You can use forkJoin for this, Dont forget to include
import { forkJoin } from 'rxjs';
forkJoin waits for each HTTP request to complete and group’s all the
observables returned by each HTTP call into a single observable array
and finally return that observable array.
getPlanet(pageIndex) {
return this.http.get < Planets > (`${this.url}${pageIndex}`, {
headers: this.headers
});
}
getAllPlanets() {
const response = [...Array(7).keys()].map(i => this.getPlanet(i));
return forkJoin(response);
}
in your component you can call getAllPlanets
this.getPlanetsService.getAllPlanets()
.subscribe(res => {
console.log(res);
}, err => {
console.log(err);
});
There are few ways you can control your async behavior.
Promises.all
Async Await
Async library
Ok there is deeper problemes here.
First, why are you trying to call the server 7 times in a row? What if I want page 200? You will make 200 Http requests? The server should return the entire list. It will increase performance and reduce complexity on client side.
Also, why getAllPlanets() return void? It's not intuitive. Instead, getAllPlanets() should return Observable<Planet[]>. All functions should either return of modify (it's part of the CQS principle) here the purpose it to return data so you can't notify your object state e.g. this.planets.push(value.results[i]). What if a invoke the function twice? Then, this.planets will contain the result of both requests.

React redux multiple requests in one action creator

I want to make 2 request in a row, kinda in a waterfall fashion. I want to first request a specific pokemon, and then based on the returned obj's payload's type, I want to request more information. I thought it would be best to separate this out to several action creators but feel weird that fetchPokemon ends with another fetch. Is this best practice?
export const fetchPokemon = function (pokemonName) {
return function (dispatch) {
dispatch(requestPokemon(pokemonName))
const requestURL = `http://pokeapi.co/api/v2/pokemon/${pokemonName}/`
return $.ajax({
url: requestURL,
}).done(function (data) {
dispatch(receivePokemon(data))
fetchPokeTypeInfo(data.types[0].type.url)
})
}
}
...
export const fetchPokemonTypeInfo = function (url) {
return function (dispatch) {
dispatch(requestPokemonTypeInfo(url))
return $.ajax({
url: url,
}).done(function (data) {
dispatch(receivePokemonTypeInfo(data))
})
}
}
I don't think there's anything particularly wrong with breaking these two up. One question I'd ask is: "Would I ever call fetchPokemonTypeInfo() directly, not from fetchPokemon()?". If not, then I'd just return the second .ajax call from the .done() function in the first. If the first call is always a dep, it seems easier to reason about what is happening if they just nest. Also, if you do want to keep them separate you'll need to pass the dispatch function as well as the url to the second function, otherwise dispatch is undefined in fetchPokemonTypeInfo().
Update:
You could nest the second call in the first like this:
export const fetchPokemon = function (pokemonName) {
return function (dispatch) {
dispatch(requestPokemon(pokemonName));
const requestURL = `http://pokeapi.co/api/v2/pokemon/${pokemonName}/`;
return $.ajax({
url: requestURL,
}).done(function (data) {
dispatch(receivePokemon(data));
dispatch(requestPokemonTypeInfo(data.types[0].type.url));
return $.ajax({
url: data.types[0].type.url,
}).done(function (data) {
dispatch(receivePokemonTypeInfo(data));
});
});
}
}
There is a way, which provides clean and predictable solution.
If you are using redux, you can make use of middleware to make your API calls. In addition, in your middleware you can extend its functionality by allowing taking multiple requests (perhaps in array), and resolving them altogether before returning success Promise.
Check this link for reference:
https://github.com/reactjs/redux/blob/master/examples/real-world/middleware/api.js
This is an functional middleware, but you have to extend it to support multiple requests :) Good luck!
Use redux-saga https://github.com/yelouafi/redux-saga
Note: the following code is just a concept, you would need to adjust it per your needs.
function* fetchPokemon(action) {
try {
//fetch1
const pokemon = yield call(Api.fetchPokemon, action.payload.pokemonId);
//this action will execute only after fetch1 is successful
yield put({type: "FETCH_POKEMON_SUCCEEDED", payload: pokemon});
//fetch2
const pokemonInfo = yield call(Api.fetchPokemonInfo, types[0].type.url)
// execute after fetch2 is successful
yield put({type: "FETCH_POKEMON_INFO_SUCCEEDED", payload: pokemonInfo})
} catch (e) {
yield put({type: "FETCH_FAILED", message: e.message});
}
}
// wait for an action and fire a saga
function* watchFetchPokemonRequest() {
yield* take("FETCH_POKEMON_REQUESTED", fetchPokemon);
}
Sagas use Generators, which "make" your async code synchronous. That way you don't need to deal with callback in promises etc. It's a nice and clean way of describing side-effect of your application.

Cancel pending API calls in Restangular

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.

How can I return a success and an error promise with and without data?

I would like to return a promise and an object called output either before or after the $http call. Can someone advise me how I can do this with the AngularJS framework and very important with Typescript so I can be sure it is working correctly?
topicNewSubmit = (): ng.IPromise<any> => {
var self = this;
var myData1 = { abc: 123 }
if (self.abc = 22) {
// How can I return an OKAY promise from here?
}
if (self.abc = 33) {
// How can I return an OKAY promise with myData1 from here?
}
if (self.abc = 88) {
// How can I return a FAIL promise from here?
}
return self.$http({ url: self.url, method: "GET" })
.then(
(response: ng.IHttpPromiseCallbackArg<any>): any => {
var myData2 = { abc: 245 }
// How can I return a promise and myData2.
// return(myData2) gives an error with Typescript
// How can I return a promise and no data
// return gives an error with Typescript
},
(error: ng.IHttpPromiseCallbackArg<any>): ng.IPromise<any> => {
var myData3 = { abc: 345 }
// Is this the correct way to return an unhandled reject with myData3
return self.$q.reject(myData);
});
}
Edit: Fixed the code and added a TypeScript Playground example. The methods are correctly typed, you can verify this by the typing errors that are thrown, try and fix them ;). I copied over the very basic required interfaces from the angular definition file.
Edit #2: Here's the fixed version of the TypeScript Playground example above.
If I understand your question correctly, you're trying to define a return type for the service method stating it returns a promise whose result upon resolving will be a certain object?
In that case you're almost there, I've split up your two example methods in separate blocks, as they require a different approach.
On a general note, I removed the scope copying (self = this) as you are using the fat arrow methods, which will automatically scope the method to the outer lexical scope. In short, there's no need to do the scope copy and in fact, in your example self doesn't always point to the service (as you're copying the scope inside of the method instead of outside of it).
Also, please note the definition of an Angular promise (truncated):
interface IDeferred<T> {
resolve(value?: T): void;
reject(reason?: any): void;
}
As such, typing an Angular Promise will only add a typing for the resolve case of the promise, not for the rejected case. Consequently, when calling your service, it will verify that the result in the success handler is of the type you have defined, but the type of the parameters in the error handler is of type any.
topicTest
For this method to work you need to inject $q into your service and then use it to create your own deferred
topicTest = (): IPromise<Foo> => { // return a promise which will result in a parameter of MyType upon resolving
var deferred = this.$q.defer<Foo>(); // Type the deferred to get better 'intellisense' support
if (this.abc = 99) {
deferred.resolve(new Foo());
}
if (this.abc = 88) {
deferred.reject("You can pass in whatever you like to the reject case");
}
return deferred.promise;
};
topicNewSubmit
The $http already returns promises, so you only need to hook into these by attaching a then callback and returning from that method to allow chaining other then callabacks to it.
In that case the return type of your service method would be angular.IPromise<() => any> where you can replace any with a type you'd like. The return type of the then method would have to correspond to whatever type you chose for the generic placeholder in the return type of the service method.
topicNewSubmit = () : IPromise<Foo> => {
return this.$http({ url: this.url, method: "GET" }).then((response): Foo => {
return new Foo();
}, (error) => {
return "whatever you'd like, it does not have to correspond to Foo";
});
}
You could then use your service like
MyService.topicNewSubmit().then((data) => {
// data needs to be of type T, where T is the type you defined in the generic placeholder of IPromise<T>
}, (error: any) => {
// In the error case, the parameters are of type any
});
To be honest, I feel like I'm shooting in the dark with your code sample, but here's my solution.
Like Anzeo, I removed the references to self or this. $q and $http should be assigned somewhere.
declare var $q: ng.IQService;
declare var $http: ng.IHttpService;
var topicNewSubmit = (): ng.IPromise<any> => {
var deferred = $q.defer<any>();
var myData1 = { abc: 123 };
if (this.abc = 22) {
deferred.resolve();
} else if (this.abc = 33) {
deferred.resolve(myData1);
} else if (this.abc = 88) {
deferred.reject();
} else {
$http({
url: this.url,
method: "GET"
})
.then(() => {
deferred.resolve({ abc: 245 });
}, () => {
deferred.reject({ abc: 345 });
});
}
return deferred.promise;
};
Answer
You cannot return something that isn't there yet unless you block all operations till you get it. Since the browser JavaScript is (mostly) single threaded you don't want to block the thread while the file downloads. Hence you return a promise that will eventually resolve. The consumer needs to use the then function to eventually get the value.
Humor
A promise is for life. Its 🐢s all the way down 🌹
You can use promise like
var deferred = $q.deferred();
$http(api url and method)
.success function() {
deferred.resolve();
}
.failure function() {
deferred.reject();
}
return deferred.promise;

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