rxjs - understand observable and observer - javascript

I can't get my head around observable and observer (rxjs), i understand that observable can dispatch the messages to observer, and observer subscribe to observable, but I dont know how to setup this?
Lets say I would like to request URL, first time the user call "loadData", the data is loaded from http request, and saved locally inside the class, next time the user call "loadData", we dont want to load from http, but get the data locally, but I would like to use the same code "loadData", and it should return Observer, so the developed don't know where and how the data is loaded!
let data = [];
function loadData():Observer {
var observer = new Observer();
if (data.length > 0) {
var observable = new Observable.from(data);
observable.add(observer);
observable.notify();
} else {
var observable = this.http.get("data.json");
observable.add(observer);
observable.readyData( (data) => {
this.data = data;
observable.notify();
};
}
}
var observer = loadData();
observer.dataComing((data) => console.log(data));
Any explanation or link to any page would be great, I understand map filter reduce in Array etc, and also the observer pattern which is easy, but not RXJS way, it's very confusing!
Thank you very much!

Here is sample of observer / observable:
var obs = Observable.create((observer) => {
setTimeout(() => {
observer.next('some event');
}, 1000);
});
obs.subscribe((event) => {
// The event is received here
});
The observer is used to trigger an event and the observable to receive it in short. Most of time, the observer is intervalle used. For example by the HTTP support of Angular2
Here are some links regarding reactive programming:
http://slides.com/robwormald/everything-is-a-stream
https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
For your particularly use case, you could use this:
loadData(url:string):Observable {
if (this.cachedData) {
return Observable.of(this.cachedData);
} else {
return this.get(...).map(res => res.map()).do((data) => {
this.cachedData = data;
});
}
}
Edit
I would refactor your code this way:
#Injectable()
export class LoungesService {
constructor(private http:Http) {
this.loungesByCity = {};
}
getLoungesByCity(city:City):Observable<any> {
if (this.loungesByCity && this.loungesByCity[city.id]) {
return Observable.of(this. loungesByCity[city.id]);
} else {
return this.http.get("lounges.json")
.map(res => <Lounge[]> res.json().data)
.map((lounges) => {
return lounges.filter((lounge) => lounge.city_id === city.id);
})
.do(data => this.loungesByCity[city.id] = data);
}
}
Note that LoungesService must be defined as shared service when bootstrapping your application:
bootstrap(AppComponent, [ LoungesService ]);

Related

How to make Javascript wait for a Promise before sending a request?

I know there are many questions and answers based on Promise, but what I want to do is to retrieve some data with axios (to a microservice) and then to use this data in order to send another request (to a different microservice).
Somehow, I figured out how to set my request:
screenshot from console with the request right before axios call
The problem is that in backend I have only first two clauses. I think this is because I have used async/await in order to successfully avoid Promise and get the actual result/class. What I meant is that, maybe the request is sent before the promise is fulfilled, but how do I correctly get the request in console?
I am newbie into Javascript, so any helping hand is welcome.
EDIT:
Here is my code:
getServicesList = async (instanceIds) => {
return await FlowsInventoryAPI.searchServices(instanceIds, this.props.salesline, this.props.environment, this.props.sources, this.props.targets)
.then((response) => {
return response;
})
.catch((error) => {
Toastr.error(ResponseErrorProvider.getError(error));
if (ResponseErrorProvider.isUnauthorized(error)) {
Toastr.error(error.response.data.message);
this.props.onLogout();
}
});
}
The above one is the first call I've talked about.
buildSearchObject = (size, page, status) => {
let interval = TimestampUtils.getInterval(this.props.logsTimeInterval);
let from = interval.from * 1000;
let to = interval.to * 1000;
return {
fromMillis: from,
toMillis: to,
size: size,
page: page,
salesline: this.props.salesline,
environment: this.props.environment,
routes: this.props.routes,
sources: this.props.sources,
targets: this.props.targets,
customFilters: [...this.props.filters.values(), ...this.getEnabledAdvancedFilters().values()],
status: status === LogStatus.ALL ? "" : status,
sortFieldName: this.props.sortColumn,
order: this.props.sortOrder,
searchFilter: this.props.searchFilter,
searchFilterOperator: this.props.searchFilterOperator,
applications: this.props.applications,
openedStores: this.props.openedStores,
servicesPromise: this.state.servicesList // here is the promise
}
};
searchLogs = (size, page, status, callback) => {
loadingService.showLoadingModal("loadingLogsPage", this.props.location.pathname);
let searchObject = this.buildSearchObject(size, page, status);
ElasticSearchApi.search(searchObject, this.props.token)
.then(response => {
callback(response);
})
.catch((error) => {
loadingService.hideLoadingModal("loadingLogsPage", this.props.location.pathname);
Toastr.error(ResponseErrorProvider.getError(error));
if (ResponseErrorProvider.isUnauthorized(error)) {
Toastr.error(error.response.data.message);
this.props.onLogout();
}
});
};
I have the second call in last paragraph which calls the buildSearchObject method which contains our promise. As I told you I figured out how to send it as value, but I think that because of "asynchronicity" maybe my promise is not ready yet in the moment when second call is called, this is why my code has the promise in state.
EDIT 2:
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = {
servicesList: this.getServicesList(this.getInstanceIds())
}
}
Here is my constructor, where I create my this.state.servicesList.
Some advice:
Do not mix traditional promises syntax with async / await. It will make your code hard to understand, even for yourself. Do not mix either callback approach with promises. Choose one approach and stick to it.
If you are having a hard time with promises, force yourself to use async / await everywhere. async / await is easier to understand in my opinion, because it doesn't break your code flow.
For instance, transform this:
FlowsInventoryAPI.searchServices(/* params */)
.then((response) => /* response code */)
.catch((error) => /* error code */)
to:
try {
const response = await FlowsInventoryAPI.searchServices(/* params */);
/* response code */
} catch (error) {
/* error code */
}
Do not make your constructors asynchronous like you do where you call this.getServicesList, because you cannot wait for an asynchronous operation (like getServicesList) inside a constructor. Use instead a static async method.
For instance, transform this:
class SomeObject extends Something {
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = {
servicesList: this.getServicesList(this.getInstanceIds())
}
}
}
to:
class SomeObject extends Something {
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = { servicesList: null };
}
async init() {
this.state.servicesList = await this.getServicesList(this.getInstanceIds());
}
static async create(props) {
const someObject = new SomeObject(props);
await someObject.init();
return someObject;
}
}
Instead of calling const object = new SomeObject(props);, do const object = await SomeObject.create(props);
You will need to use await keyword to wait for a promise response before continuing.
// 1. Wait for create or update the customer before continuing
const customerId = await updateOrCreateCustomer(customerData);
// 2. Register sale, with customer created in previous section
const customerSale = sale(paymentMethod, customerId);
Read more about the await keyword

Node class start function

I am new to javascript/node and I am building a class that calls the Telegram api every second to get updates and store so I can have other function use that data. I have pieced together code form examples but I am getting an error when I call bot.start(); because no function is being passed to getUpdates. I am not sure why the (fn) is needed.
class Bot {
constructor(token) {
let _baseApiURL = `https://api.telegram.org`;
this.baseApiURL = _baseApiURL;
this.token = token;
}
start(){
this.getUpdates();
}
getBaseApiUrl(){
return this.baseApiURL;
}
getToken(){
return this.token;
}
getAPI(apiName) {
return axios.get(`${this.getApiURL()}/${apiName}`);
}
getApiURL() {
return `https://api.telegram.org/bot${this.getToken()}`;
}
getUpdates(fn) {
this.getAPI('getUpdates')
.then(res => {
this.storeUpdates(res.data);
fn(res.data);
setTimeout(() => {
this.getUpdates(fn);
}, 1000);
})
.catch(err => {
console.log('::: ERROR :::', err);
});
}
storeUpdates(data){
console.log(data);
}
}
const bot = new Bot(TOKEN);
bot.start();
Its not clear what exactly you are trying to achieve with that fn method. You are not passing any method, therefore it fails. This would work
getUpdates() {
this.getAPI('getUpdates')
.then(res => {
this.storeUpdates(res.data);
setTimeout(() => {
this.getUpdates();
}, 1000);
})
.catch(err => {
console.log('::: ERROR :::', err);
});
}
If you want to implement some kind of Observer pattern, you should not couple it with getUpdates method, just create methods for registering observers and notify them when store is changed.
Also the way how you trigger the repetition is not too good, because once you get error (and with HTTP methods you usually get some ERROR sooner or later) it will break the whole flow.
Use something like https://www.npmjs.com/package/cron to trigger periodic actions.

How to avoid sending multiple duplicate AJAX requests in axios

Is it possible to automatically throttle all requests going to a particular list of endpoints using axios? Perhaps using axios interceptor?
Currently I throttle the user action that sends the axios request, but the problem with that is that I have to write this everywhere I have a user action that results in some AJAX request. Like this
const throttledDismissNotification = throttle(dismissNotification, 1000)
const dismiss = (event: any) => {
throttledDismissNotification();
};
render() {
return (
<Button onClick={dismiss}>Dismiss Notification</Button>
)
}
This results in a lot of clutter and I was wondering if this could be automated.
Something like:
if(request.url in listOfEndpointsToThrottle && request.params in cacheOfPreviousRequestsToThisEndpoint) {
StopRequest();
}
Obviously this is pseudocode but you get the idea.
Perhaps you could try to use the Cancellation feature that axios provides.
With it, you can ensure that you don't have any two (or more, depending on your implementation) similar requests in a pending state.
Below, you will find a small simplified example of how to ensure that only the latest request is processed. You can adjust it a bit to make it function like a pool of requests
import axios, { CancelToken } from 'axios';
const pendingRequests = {};
const makeCancellable = (headers, requestId) => {
if (!requestId) {
return headers;
}
if (pendingRequests[requestId]) {
// cancel an existing request
pendingRequests[requestId].cancel();
}
const source = CancelToken.source();
const newHeaders = {
...headers,
cancelToken: source.token
};
pendingRequests[requestId] = source;
return newHeaders;
};
const request = ({
url,
method = 'GET',
headers,
id
}) => {
const requestConfig = {
url,
method,
headers: makeCancellable(headers || {}, id)
};
return axios.request(requestConfig)
.then((res) => {
delete pendingRequests[id];
return ({ data: res.data });
})
.catch((error) => {
delete pendingRequests[id];
if (axios.isCancel(error)) {
console.log(`A request to url ${url} was cancelled`); // cancelled
} else {
return handleReject(error);
}
});
};
export default request;
It's quite easy to throttle an axios request itself. The real headache is how to handle the promises that are returned from nullified requests. What is considered sane behavior when dealing with promises that are returned from a nullified axios request? Should they stay pending forever?
I don't see any perfect solution to this problem. But then I come to a solution that is kind of cheating:
What if we don't throttle the axios call, instead we throttle the actual XMLHttpRequest?
This makes things way easier, because it avoids the promise problem, and it's easier to implement. The idea is to implement a cache for recent requests, and if a new request matches a recent one, you just pull the result from cache and skip the XMLHttpRequest.
Because of the way axios interceptors work, the following snippet can be used to skip a certain XHR call conditionally:
// This should be the *last* request interceptor to add
axios.interceptors.request.use(function (config) {
/* check the cache, if hit, then intentionally throw
* this will cause the XHR call to be skipped
* but the error is still handled by response interceptor
* we can then recover from error to the cached response
**/
if (requestCache.isCached(config)) {
const skipXHRError = new Error('skip')
skipXHRError.isSkipXHR = true
skipXHRError.request = config
throw skipXHRError
} else {
/* if not cached yet
* check if request should be throttled
* then open up the cache to wait for a response
**/
if (requestCache.shouldThrottle(config)) {
requestCache.waitForResponse(config)
}
return config;
}
});
// This should be the *first* response interceptor to add
axios.interceptors.response.use(function (response) {
requestCache.setCachedResponse(response.config, response)
return response;
}, function (error) {
/* recover from error back to normality
* but this time we use an cached response result
**/
if (error.isSkipXHR) {
return requestCache.getCachedResponse(error.request)
}
return Promise.reject(error);
});
I have a similar problem, thru my research it seems to lack a good solution. All I saw were some ad hoc solutions so I open an issue for axios, hoping someone can answer my question https://github.com/axios/axios/issues/2118
I also find this article Throttling Axios requests but I did not try the solution he suggested.
And I have a discussion related to this My implementation of debounce axios request left the promise in pending state forever, is there a better way?
I finish one, #hackape thank you for you answer, the code is as follows:
const pendings = {}
const caches = {}
const cacheUtils = {
getUniqueUrl: function (config) {
// you can set the rule based on your own requirement
return config.url + '&' + config.method
},
isCached: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
return caches[uniqueUrl] !== undefined
},
isPending: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
if (!pendings[uniqueUrl]) {
pendings[uniqueUrl] = [config]
return false
} else {
console.log(`cache url: ${uniqueUrl}`)
pendings[uniqueUrl].push(config)
return true
}
},
setCachedResponse: function (config, response) {
let uniqueUrl = this.getUniqueUrl(config)
caches[uniqueUrl] = response
if (pendings[uniqueUrl]) {
pendings[uniqueUrl].forEach(configItem => {
configItem.isFinished = true
})
}
},
getError: function(config) {
const skipXHRError = new Error('skip')
skipXHRError.isSkipXHR = true
skipXHRError.requestConfig = config
return skipXHRError
},
getCachedResponse: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
return caches[uniqueUrl]
}
}
// This should be the *last* request interceptor to add
axios.interceptors.request.use(function (config) {
// to avoid careless bug, only the request that explicitly declares *canCache* parameter can use cache
if (config.canCache) {
if (cacheUtils.isCached(config)) {
let error = cacheUtils.getError(config)
throw error
}
if (cacheUtils.isPending(config)) {
return new Promise((resolve, reject) => {
let interval = setInterval(() => {
if(config.isFinished) {
clearInterval(interval)
let error = cacheUtils.getError(config)
reject(error)
}
}, 200)
});
} else {
// the head of cacheable requests queue, get the response by http request
return config
}
} else {
return config
}
});

admin-on-rest how to implement a custom saga for auto-refresh

In the example provided in the aor-realtime readme
import realtimeSaga from 'aor-realtime';
const observeRequest = (fetchType, resource, params) => {
// Use your apollo client methods here or sockets or whatever else including the following very naive polling mechanism
return {
subscribe(observer) {
const intervalId = setInterval(() => {
fetchData(fetchType, resource, params)
.then(results => observer.next(results)) // New data received, notify the observer
.catch(error => observer.error(error)); // Ouch, an error occured, notify the observer
}, 5000);
const subscription = {
unsubscribe() {
// Clean up after ourselves
clearInterval(intervalId);
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
},
};
};
const customSaga = realtimeSaga(observeRequest);
fetchData function is mentioned, but it's not accessible from that scope, is it just a symbolic/abstract call ?
If I really wanted to refresh the data based on some realtime trigger how could i dispatch the data fetching command from this scope ?
You're right, it is a symbolic/abstract call. It's up to you to implement observeRequest, by initializing it with your restClient for example and calling the client methods accordingly using the fetchType, resource and params parameters.
We currently only use this saga with the aor-simple-graphql-client client

Copying response data from rxJs in Angular2/Typescript

I'm using Observable from rxJS in my Angular2 with TypeScript application. I would like to take a copy of the http get response data.
Service:
getSizes(sku: number): Observable<SizeList[]> {
let api = this.host + this.routes.sizes + sku;
return this._http.get(api)
.map((response: Response) => <SizeList[]>response.json())
.catch(this.handleError);
}
Component:
getSizes() {
this._productService.getSizes(this.productColour)
.subscribe(
sizes => this.sizes = sizes,
error => this.errorMessage = <any>error);
}
How can I take a copy of this.sizes? If I try to take a copy at the end of my components getSizes(), it's undefined.
I think that your problem is related to the asynchronous aspect of observables. At the end of the getSizes method, the data are there yet. They will be available within the subscribe callback:
getSizes() {
this._productService.getSizes(this.productColour)
.subscribe(
sizes => {
this.sizes = sizes;
console.log(this.sizes); // <------
},
error => this.errorMessage = <any>error);
}
If you want to return the value from the getSizes method, you need to return an observable and let the method caller subscribe on it:
getSizes() {
return this._productService.getSizes(this.productColour)
.catch(error => this.errorMessage = <any>error);
}
someOtherMethod() {
this.getSizes().subscribe(sizes => this.sizes = sizes);
}
This is because HTTP requests are made asynchronously in JS/Angular 2, so logic at the end of your getSizes() method is probably running before the method this._productService.getSizes(...) has finished loading your content.
You should place your logic therefore in the subscribe() method like this:
getSizes() {
this._productService.getSizes(this.productColour)
.subscribe(
sizes => {
this.sizes = sizes
// more logic here
},
error => this.errorMessage = <any>error);
// code here gets executed before the subscribe() method gets called
}

Categories