Im fairly new to RxJs and I would like to understand what the best way is to work with Rx in combination with Promises.
What I want to create is a service in Angular that acts much as an event dispatcher pattern and emits an event once a promise is complete. What I also require is that, if there are no (event) subscribers the observable never gets called. The last thing I want to happen is that any subsequent subscribers to the observable get the same result without triggering another request to the server.
I have managed to implement my own solution here:
// ... CountryService code
var COUNTRIES_LOADED = Rx.Observable
.create(function (observer) {
$http
.get('/countries')
.then(function (res) {
observer.onNext(res);
}, function (err) {
observer.onError(err);
})
.finally(function () {
observer.onCompleted();
});
})
.shareReplay();
Now anytime I subscribe a new "listener" to subject the observable will be pulled. Any new subscribers will get the value cached without touching the server again.
So inside my "consumer" (Angular Directive) I would like to do something like this:
// ... countryInput directive code:
COUNTRIES_LOADED.subscribe(function (response) {
// Fill in countries into scope or ctrl
scope.countries = response.countries;
});
Any future subscribers to the COUNTRIES_LOADED observer MUST NOT trigger an $http request. Likewise, if the directive is never included on the page, $http will never get called.
The solution above works, however I am not aware of the potential drawbacks and memory implications of this approach. Is this a valid solution? Is there a better / more appropriate way to achieve this using RxJs?
Many thanks!
Use Rx.Observable.fromPromise(promise)
fromPromise:
Converts a Promises/A+ spec compliant Promise and/or ES2015 compliant
Promise or a factory function which returns said Promise to an
Observable sequence.
example:
var source = Rx.Observable.fromPromise(promise);
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
update
rxjs6 method is from
update
As of rxjs6 you can use from()
Did you tried to use the fromPromise() API of rxjs5 ?
Check it's documentation here !
I found the answer here (Just slightly differently named)
rxjs using promise only once on subscribe
So for my example the answer is as simple as:
var loadCountries = function () { return $http.get('/countries'); };
var observable = Rx.Observable.defer(loadCountries).shareReplay();
This is how you can use Observables
Lets say you have a method called getuser(username).
//Returns an observable
getUser(username){
return $http.get(url)
.map(res => res.json());
}
And you can use it as below
getUser.subscribe(res => console.log(response));
BUT if you want to use promises
//Returns an Promise
//Donot forget to import toPromise operator
getUser(username){
return $http.get(url)
.map(res => res.json())
.toPromise();
}
And you can use it as below
getUser.then(res => console.log(response));
Related
Since inside a Observable we have an option of calling Promise ,I have a clarification regrading this.
Since a promise gets executed immediately after declaration , will it be executed without attaching Subscribe method to it ?
And also since it cannot be cancelled why would anyone think of calling a Promise inside a observable .
Rx.Observable.fromPromise():
const computeFutureValue = new Promise((resolve, reject) => {
//make http api call
});
Rx.Observable.fromPromise(computeFutureValue)
.subscribe(
val => {
console.log(val);
},
err => {
console.log(`Error occurred: ${err}`);
},
() => {
console.log('All done!');
});
As you said, a Promises body is executed when you create the Promise.
So when you create this Promise:
const computeFutureValue = new Promise((resolve, reject) => {
//make http api call
});
the http request is executed no matter what you do next. Using from (or fromPromise) to convert the Promise to an Observable and subscribing to this Observable doesn't affect the Promise and it's execution in any way.
You'd want to convert a Promise to an Observable if you want to be able to work with all the operators Observables offer or because your app works with and requires Observables at certain points.
If you only want to create and execute a Promise when you subscribe to an Observable you can use defer, see my answer here: Convert Promise to Observable
Is it a good practice to convert the observable object to a promise since observable can be used in almost all the occasions instead of promise?
I've started to learn angular recently and come across the below code snippet in a new project(Angular 5) at my workplace. This code snippet is used to load a list of data such as a customer list. This customer list data set is received as a one time action, not as a stream. Therefore it has no technical limitation to use promise. But I would like to know whether there are any drawbacks or limitations.
getViewDataForPage(): Promise<any> {
return this.commonDataService.getViewDataForPage(args_set)
.toPromise()
.catch(error => this._exceptionService.catchBadResponse(error));
}
//in commonDataService.ts
getViewDataForPage(args_set): Observable<any> {
/** logic goes here */
return this.httpConnection.post(viewDataRequest, args);
}
It depends on your requirement, technically observables are better than promises because they provide the features of Promise and more. With Observable, it doesn't matter if you want to handle none to multiple events.
Observables are cancelable ie., using unsubscibe() you can cancel an observable irrespective of its state.
Promises on the other hand deal with only 1 async event ie.., either it will resolve or reject if error occurs
A good place for Promise would be if you have a single even to process for example.
let connect=new Promise((resolve,reject)=>{
if( connection Passed){
resolve("Connected");
} else{
reject("failed");
}
}
You can use both. The Observable is used when you are going to receive different values during the time is more like when you are a subscriptor in a some magazine, when ever the mazagazine has new edition you will be notifier and the same happens to all subscriptors and everybody receives the new value or in this case the new magazine.
In the case of the Promise it is Async like an observable but it just happen ones.
Then if the following code will happens ones in this case is ok the promise
getViewDataForPage(): Promise<any> {
return this.commonDataService.getViewDataForPage(args_set)
.toPromise()
.catch(error => this._exceptionService.catchBadResponse(error));
}
//in commonDataService.ts
getViewDataForPage(args_set): Observable<any> {
/** logic goes here */
return this.httpConnection.post(viewDataRequest, args);
}
I'm banging my head against the wall with observables. Almost all of the documentation I can find is in the older rxjs syntax.
I have an API call which is an observable. I'm calling it elsewhere and subscribing to it - trying to populate a table with the data from this GET request.
If I simply console.log my getData function, it logs the subscription rather than my data.
I can successfully console.log data within the .subscribe function, but I want to use data outside of .subscribe().
How do I extract data out of the .subscribe() function and use it elsewhere? Or, must all of my logic be contained within the .subscribe() function to use data?
getData2() {
return this.m_dbService.get('api/myApiPath').subscribe(
data => (console.log(data)), //This properly logs my data. How to extract `data` out of here and actually use it?
error => { throw error },
() => console.log("finished")
);
}
workbookInit(args){
var datasource = this.getData2(); // this returns the subscription and doesn't work.
}
just return the HTTP req from getData() and subscribe it inside the workbookInit function.
getData2() {
return this.m_dbService.get('api/myApiPath')
}
workbookInit(args){
this.getData2().subscribe(
data => {
var datasource = data
},
error => { throw error },
() => console.log("finished")
}
What you probably want to do is to populate another Observable with the data so that you can access it elsewhere in your project without the need for calling the API more than once.
To do this, you create what is known as a Subject (in this case a BehaviorSubject) and you can populate that with data when your API call returns a response.
Then, in order to access this data elsewhere, you can create a "get" function to return the Subject (which is itself an Observable) whenever you need the data.
Here is an example:
my-data.service.ts
myData: BehaviorSubject<number> = new BehaviorSubject<number>(0);
callApi() {
this.dbService.get('apiUrl').subscribe(
(data) = > this.myData.next(data) // Assuming data is a 'number'
);
}
getMyData() {
return this.myData.asObservable();
}
Now to use this in a component:
this.myService.getMyData().subscribe(
(data) => { /* Use the value from myData observable freely */ }
);
Or you could rely on the Angular async pipe (which is a very convenient method for dealing with observables in your code).
You should not subscribe to the Observable inside getData2. Return it as is instead, then do the following:
var dataSource;
this.getData2().subscribe(res => dataSource = res);
Please note that the variable dataSource will be set when the request is done (asynchronously), so you can't use it immediately in the same block scope.
If you want to use it immediately, then put your code inside the subscription.
If you have an observable that provides data to populate a table, the best way is not to use subscribe(), but use the observable directly in your html template by using the async pipe. You'll have less to worry about and your code will be much simpler.
I run into this every now and then:
return somethingThatReturnsAPromise()
.then((response) => {
soSomethingg(); // Eg; update the UI
return response;
});
Now I'm looking for something that is not expected to return anything and won't change the promise chain if I forget that:
return somethingThatReturnsAPromise()
.whatImLookingFor((response) => {
doSomething(); // Eg; update the UI
})
.then((response) => {
// and this one should still be able to access response
});
Maybe this goes against the idea of promises, but for me, it's a bit inconvenient since I can't pass arbitrary functions.
One idea is to compose a function:
const sideEffect = (callback) => {
return (response) => {
callback(response);
return response;
};
};
And I could use it as
return somethingThatReturnsAPromise()
.then(sideEffect(doSomething));
But I'd prefer something instead of then is there something like that?
Note: I'm working with Angular 1.x so I need something like for that.
I would assume that you're not really writing .then().then(), because you could collapse that into a single .then, but that your concern is really about returning the promise and having some external code add another then to the chain. In that case do this:
let p = somethingThatReturnsAPromise();
p.then(() => doSomething());
return p;
This allows the caller to attach additional thens to the original promise instead of chaining off of your .then, thereby receiving the original promise's value. This is called branching the promise chain.
Maybe this goes against the idea of promises
Slightly, promise chains are pipelines where then handlers transform things at each stage. But it's perfectly valid to want to pass through the value unchanged.
One idea is to compose a function:
Indeed the first thing that came to mind, and how I'd do it.
But I'd prefer something instead of then is there something like that?
There isn't. You could add it for your own projects (I wouldn't in a library) by adding it to Promise.prototype. Or you could give yourselve a Promise subclass and add it there.
With a Promise sublass you'd do something like:
return MyPromise.resolve(somethingThatReturnsAPromise())
.thenSide(soSomethingg); // Eg; update the UI
...where thenSide is your method that's then but passing the original value back unchanged, e.g.:
class MyPromise extends Promise {
thenSide(callback) {
this.then(callback);
return this;
}
}
or
class MyPromise extends Promise {
thenSide(callback) {
this.then(callback);
return MyPromise.resolve(this);
}
}
...depending on whether you're bothered about thenSide returning the same promise (since then always returns a new one).
As far as I know (I could well be wrong) the wrapper method for "pass-through" side-effects is an idiomatic way to do what you want.
Alternatively (if you need the same response in multiple places) you can break up the promise chain when you encounter a situation like this.
I would like to to something like:
this._myService.doSomething().subscribe(result => {
doSomething()
});
.then( () => dosthelse() )
.then( () => dosanotherthing() )
So I would like to chain .then like in promise. How would I do that in Rxjs?
this._myService.getLoginScreen().subscribe( result => {
window.location.href = MyService.LOGIN_URL;
/// I would like to wait for the site to load and alert something from the url, when I do it here it alerts the old one
});
.then (alert(anotherService.partOfTheUrl())
getLoginScreen() {
return this.http.get(myService.LOGIN_URL)
.flatMap(result => this.changeBrowserUrl())
.subscribe( result => //i want to do sth when the page is loaded//);
}
changeBrowserUrl(): Observable<any> {
return Observable.create( observer => {
window.location.href = myService.LOGIN_URL;
observer.next();
});
}
The equivalent of then for observables would be flatMap. You can see some examples of use here :
RxJS Promise Composition (passing data)
Why we need to use flatMap?
RxJS sequence equvalent to promise.then()?
For your example, you could do something like :
this._myService.doSomething()
.flatMap(function(x){return functionReturningObservableOrPromise(x)})
.flatMap(...ad infinitum)
.subscribe(...final processing)
Pay attention to the types of what your functions return, as to chain observables with flatMap you will need to return a promise or an observable.
If dosthelse or dosanotherthing returns a raw value, the operator to use is map. If it's an observable, the operator is flatMap (or equivalent).
If you want to do something imperatively. I mean outside the asynchronous processing chain, you could leverage the do operator.
Assuming that dosthelse returns an observable and dosanotherthing a raw object, your code would be:
this._myService.doSomething()
.do(result => {
doSomething();
})
.flatMap( () => dosthelse() )
.map( () => dosanotherthing() );
Notice that if you return the return of the subcribe method, it will correspond to a subscription object and not an observable. A subscription object is mainly for being able to cancel the observable and can't take part of the asynchronous processing chain.
In fact, most of the time, you subscribe at the end of the chain.
So I would refactor your code this way:
this._myService.getLoginScreen().subscribe( result => {
window.location.href = MyService.LOGIN_URL;
/// I would like to wait for the site to load and alert something from the url, when I do it here it alerts the old one
alert(anotherService.partOfTheUrl()
});
getLoginScreen() {
return this.http.get(myService.LOGIN_URL)
.flatMap(result => this.changeBrowserUrl())
.do( result => //i want to do sth when the page is loaded//);
}
changeBrowserUrl(): Observable<any> {
return Observable.create( observer => {
window.location.href = myService.LOGIN_URL;
observer.next();
});
}
Updated rxjs solution
Rxjs has changed quite a bit since this was answered.
flatMap is now mergeMap
Or switchMap, they're mostly interchangeable but it's good to know the difference
.do() is now tap()
Chaining is now done inside of a .pipe(). All manipulation should be done inside this pipe
You can chain pipes if needed (Ex. one variable maps an array of Users. Another variable takes that first variable and maps it a second time)
Do something after the original call has been made
Scenario
Make an HTTP call (Ex. Authentication check)
When that call has finished, navigate to another page
this._myService.getAuthenticated()
.pipe(
tap(result => this._myService.navigateToHome())
)
.subscribe()
Chain multiple calls
Scenario
Make an HTTP call (Ex. Authentication check)
Make a 2nd call to pull more info
Navigate after both calls have finished
this._myService.getAuthenticated()
.pipe(
// The Authentication call returns an object with the User Id
switchMap(user => this._myService.getUserInfo(user.id))
// After the user has been loaded, navigate
tap(user => this._myService.navigateToHome())
)
.subscribe()
Note on the above examples: I am assuming these calls are HTTP which unsubscribe after being called once. If you use a live observable (ex. a stream of Users), make sure you either unsubscribe or use takeUntil/first operators.
Example for Clarification (April, 2022)
The top of this pipe can emit n values (this means the chain will be called everytime a new value enters into the top of the pipe). In this example, n equals 3. This is a key difference between observables and promises. Observables can emit multiple values over time, but a promise cannot.
The subsequent chained streams emit one value (hence mimicing promises).
// Emit three values into the top of this pipe.
const topOfPipe = of<string>('chaining', 'some', 'observables');
// If any of the chained observables emit more than 1 value
// then don't use this unless you understand what is going to happen.
const firstObservable = of(1);
const secondObservable = of(2);
const thirdObservable = of(3);
const fourthObservable = of(4);
const addToPreviousStream = (previous) => map(current => previous + current);
const first = (one) => firstObservable.pipe(addToPreviousStream(one));
const second = (two) => secondObservable.pipe(addToPreviousStream(two));
const third = (three) => thirdObservable.pipe(addToPreviousStream(three));
const fourth = (four) => fourthObservable.pipe(addToPreviousStream(four));
// Pipeline of mergeMap operators, used for chaining steams together.
topOfPipe.pipe(
mergeMap(first),
mergeMap(second),
mergeMap(third),
mergeMap(fourth),
).subscribe(console.log);
// Output: chaining1234 some1234 observables1234
You could also use concatMap or switchMap. They all have subtle differences. See rxjs docs to understand.
mergeMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap
concatMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/concatmap
switchMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap