Async
Asynchrony refers to the occurrence of events independently of the main program flow and ways to deal with such events
Where do i get the code?
https://github.td.teradata.com/ux/covalent-training/commits/feature/observables
Setup
Stash away any items not needed for this lab
git stash
Checkout the repo
git checkout 4239960507304b1aefefe2acc981966db97873ee
start the local angular-cli server:
git serve
you should see
** NG Live Development Server is running on http://localhost:4200. **
Lets get started!
First we are gonna dive into the code and notice we are loading the MessageComponent
with a synchronous call.
this.message = this._messagesService.load(id);
This is normally fine in most cases like validations, and things you can check in the client. But what if i need to request something from the server or execute something that will take a long time?
What is a Promise?
A Promise handles a single event when an async operation completes or fails.
Promises are good for solving asynchronous operations such as querying a service with an XMLHttpRequest, where the expected behavior is one value and then completion.
load(id: number): Promise<IMessage> {
return new Promise((resolve: Function) => {
setTimeout(() => {
resolve({
id: 1,
title: 'my title',
sender: 'ed morales',
body: 'my message',
received_at: new Date(),
});
}, 2000);
});
}
Example:
https://github.td.teradata.com/ux/covalent-training/commit/ac5309fa87196e7178dd8ea79ccf611dd0411fda
Now that our service returns a promise (mocked), we can leverage it and set a loading mask while the execution is on stand by (and even do other things).
this._messagesService.load(id).then((message: IMessage) => {
this.message = message;
this._loadingService.resolve('message.info');
});
Example:
https://github.td.teradata.com/ux/covalent-training/commit/9fafef45a5f425772081d0ee20a84a95ce0cb67f
Promises might fail every now and then (exceptions, failed requests, etc etc), so we need to watch out for those cases too. We can leverage the reject()
/ catch()
mechanism from a promise for exactly those cases.
load(id: number): Promise<IMessage> {
return new Promise((resolve: Function, reject: Function) => {
setTimeout(() => {
if (id) {
resolve({
id: 1,
title: 'my title',
sender: 'ed morales',
body: 'my message',
received_at: new Date(),
});
} else {
reject('error from backend');
}
}, 2000);
});
}
and not we just listen to the catch()
callback:
this._messagesService.load(id).then((message: IMessage) => {
this.message = message;
this._loadingService.resolve('message.info');
}).catch((error: string) => {
this._errorService.open({error: 0, message: error});
this._loadingService.resolve('message.info');
});
Example:
https://github.td.teradata.com/ux/covalent-training/commit/bf182715b4a83352dcfaad8640ea867fb1204d30
What is an Observable?
AnObservable
is like aStream
(in many languages) and allows to pass zero or more events where the callback is called for each event.
Often Observable
is preferred over Promise
because it provides the features of Promise
and more. With Observable
it doesn't matter if you want to handle 0, 1, or multiple events. You can utilize the same API in each case. It also has the advantage over Promise
to be cancelable among other powerful features.
load(id: number): Observable<IMessage> {
return new Observable<IMessage>((subscriber: Subscriber<IMessage>) => {
setTimeout(() => {
if (id) {
subscriber.next({
id: 1,
title: 'my title',
sender: 'ed morales',
body: 'my message',
received_at: new Date(),
});
subscriber.complete();
} else {
subscriber.error('error from backend');
}
}, 2000);
});
}
And now we have to modify the MessageComponent
to expect an Observable
.
this._messagesService.load(id).subscribe((message: IMessage) => {
this.message = message;
this._loadingService.resolve('message.info');
}, (error: string) => {
this._errorService.open({error: 0, message: error});
this._loadingService.resolve('message.info');
});
Example:
https://github.td.teradata.com/ux/covalent-training/commit/caa04dbe48f9c68a58d4b8519fd5be9fddc507ac
Leverage an Observable
.Almost anything (if not everything) that a promise can do, you can do it with an Observable. But why is an Observable better than .a promise?
For the most part, they are interchangeable and in normal cases you can use one or the other and not feel a difference. Observables shine most when we need to cancel them, retry them or even have multiple listeners into the same observable event (plus additional operations in the rxjs
lib).
We are now going to subscribe twice into the observable to look at this behavior.
let observable: Observable<IMessage> = this._messagesService.load(id);
observable.subscribe((message: IMessage) => {
this.message = message;
this._loadingService.resolve('message.info');
}, (error: string) => {
this._errorService.open({error: 0, message: error});
this._loadingService.resolve('message.info');
});
observable.subscribe((message: IMessage) => {
console.log('i also listened');
});
Not what we expected?
Example:
https://github.td.teradata.com/ux/covalent-training/commit/7c352ff08fb7384e57794d4241d9673d929efb42
Subject to the rescue
A Subject is a special type of Observable that allows values to be multicasted to many Observers
We leverage the power of Subject's to execute the code once and notify all the subscribed objects listening to our observable.
private _subject: Subject<IMessage> = new Subject<IMessage>();
private _observable: Observable<IMessage> = this._subject.asObservable();
load(id: number): Observable<IMessage> {
setTimeout(() => {
console.log('i was executed');
if (id) {
this._subject.next({
id: 1,
title: 'my title',
sender: 'ed morales',
body: 'my message',
received_at: new Date(),
});
this._subject.complete();
} else {
this._subject.error('error from backend');
}
}, 2000);
return this._observable;
}
What the previous code does is:
- It create a single Subject instance to control everything.
- It creates a single Observable linked to that Subject instance.
- Every time somebody called the
load()
method, it returns the same observable instance. - Once the timeout is completed, it calls its
next()
method and notifies every registered object.
Pretty cool, huh?
Example:
https://github.td.teradata.com/ux/covalent-training/commit/2a79d4f972d33f2835b804d6493ed155576cccc4