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. **

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?

AnObservableis like aStream(in many languages) and allows to pass zero or more events where the callback is called for each event.

Often Observableis preferred over Promisebecause it provides the features of Promiseand more. With Observableit 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 Promiseto 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 theload() method, it returns the same observable instance.
  • Once the timeout is completed, it calls itsnext() method and notifies every registered object.

Pretty cool, huh?

Example:

https://github.td.teradata.com/ux/covalent-training/commit/2a79d4f972d33f2835b804d6493ed155576cccc4

results matching ""

    No results matching ""