Promise and observables

Influenced by Promise, we often compare Observable and Promise.

observable.subscribe(function next(val) {});

promise.then(function resolve(val) {});
Copy the code

Next will be called multiple times, resolve will be called only once. Or promise can be an Observable that emits value only once.

Of course, subscribe and then are certainly not exactly equivalent. One of the biggest differences is that then only treats the understanding of a Promise resolve and does not trigger the promise’s execution. That is, a promise is executed with or without a THEN, but an Observable is executed without a subscribe. Then more closely resembles swicthMap in Observable.

So we could also simply implement toPromise:

class Observable { ... . toPromise() { return new Promise((resolve) => { this.take(1).subscribe(resolve); }); }}Copy the code

First pit: An Observable is never executed until it is subscribed.

So here’s the first one. In the beginning, we write observables as promises. For example, when we don’t need to process API results, we don’t write then.

Fetch (' XXXXXXXXXXX /user', {method: "POST", body: "",})Copy the code

There is no problem with promises; the user can update, but we don’t care if it succeeds.

But for Observables, we write it the same way:

FromFetch (' XXXXXXXXXXX /user', {method: "POST", body: "",})Copy the code

Nextwork has no record of API calls at all. It’s easy to see why:

FromFetch (' XXXXXXXXXXX /user', {method: "POST", body: "",}).subscribe();Copy the code

Subscribe can never be omitted. We can simply think of promises as observables that subscribe when they are created, and the value will emit only once.

Second pitfall: Each subscribe creates a new execution space, which needs to be paid attention to to avoid unnecessary resource consumption.

If my previous API call cared about the results and wanted to print the results:

Const updateUser = fetch(' XXXXXXXXXXX /user', {method: "POST", body: "",}); updateUser.then((val) => console.log('print api result1', val)); // xxxxxxxx // xxxxxxxx updateUser.then((val) => console.log('print api result2', val));Copy the code

That’s fine, you can see that ‘print API result1’, ‘print API result2’ is printed once.

But what about an Observable instead:

Const updateUser = fromFetch(' XXXXXXXXXXX /user', {method: "POST", body: "",}); updateUser.subscribe((val) => console.log('print api result1', val)); // xxxxxxxx // xxxxxxxx updateUser.subscribe((val) => console.log('print api result2', val));Copy the code

You can see, ‘print API result1’, ‘print API result2’ is printed once. But at the same time, you’ll notice that the Ajax call is sent twice!

It’s easy to understand because an Observable is essentially a function, and every subscribe gets a new execution space. So, both subscribe will be independent of each other and print out their own data.

So the Promise definition can be updated to an Observable that subscribes when it is created, and the value will emit only once. And can only be subscribed once.

The third pit, exception handling in Observable:

For example, there is a button on the page called Update, which calls the API Update user when clicked.

const saveUser$ = fromEvent(document.querySelector('.saveButton'), 'click') .pipe( switchMap(() => updateUser()), Function updateUser() {return fromFetch(' XXXXXXXXXXX /user', {method: "POST", body: "",}); }Copy the code

On the surface, there seems to be no problem. In fact, if the API call does not have 2xx, the fetch will throw exception. If the exception is not properly caught, it will throw up.

event$ ---0----------0-------------0---------------- \ \ \ \ \ \ ajax call ---1-| ---1-| --1-| saveUser$ -- -- -- -- -- -- -- -- 1 -- -- -- -- -- -- -- -- -- -- 1 -- -- -- -- -- -- -- -- -- -- -- -- -- 1 -- -- -- -- -- -- -- -- -- --Copy the code

Suppose the second Ajax call throws an error:

  event$ ---0----------0-------------0----------------
             \          \              \
              \          \              \
ajax call     	---1-|     ---x-|         --1-|
saveUser$  --------1----------x-|-----------------------
Copy the code

Because an Error stops an Observable from receiving any new values. The symptom is that if the API returns anything other than 2xx once, no matter how much you click the button afterwards, it will have no effect.

RXJS catchError (swicthMap, swicthMap, swicthMap) is a high order operator.

CatchError will return a new stream because the current stream has already ended during the catchError. Equivalent to switchMap to another stream when error occurs.

We can simply understand as:

function catchError(observable, catchErrorFun) { return new Observable((observer) => { observable.subscribe({ error: (err) => { catchErrorFun(err).subscribe(observer); }}); }); }Copy the code

So the previous example can be handled like this:

const saveUser$ = fromEvent(document.querySelector('.saveButton'), 'click') .pipe( switchMap(() => updateUser()).pipe( catchError((err) => { console.log(err); return empty(); })),Copy the code

This avoids the end of the downstream stream caused by a throw error.

The high order operator is the high order operator. In addition, ajax calls will have multiple subscribe times, resulting in a waste of resources. If we need to subscribe more than once, what will we do? We’ll design transitions to and from cold <=> hot, but more on that later.