Home > Enterprise >  Observable gets the wrong value when using the from operator with a promise after immediately updati
Observable gets the wrong value when using the from operator with a promise after immediately updati

Time:10-16

I have an observable that listens to route changes :) I also have a promise that clears my local storage and when it's done I immediately change the route but my switchMap/switchMapTo inside my route-change observable get the old value.. why does this happen?

I have tried many ways to solve this - I found two that work :) But I wish to understand why they worked. Could anyone please help? Is this something regarding hot/cold observables issues? Perhaps something with the event loop? I really don't know :)

    this.storageService.clear().then((_) => {
      // this was successful!! And it was checked - user is deleted :)
      // now change the route and trigger the observable below :)
    });
    
    this.router.events.pipe(
      filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd)
      /*
        switchMap in here to get the user!
        Options 1   2 show that the user info exists!! 
        Although I know for sure that it was deleted.
        Option 3   4 work.
      */
    
      // 1) switchMapTo(from(this.storageService.getAsync<IUser>('user'))),
    
      // 2) switchMapTo(this.storageService.getAsync<IUser>('user')),
    
      // 3) switchMap(async (_) => {
      //      const y = await this.storageService.getAsync<IUser>('user');
      //      return y;
      //    }),
    
      // 4) switchMapTo(defer(() => this.storageService.getAsync<IUser>('user'))),
    );


    // Question edited for extra information - this is the getAsync function
    async getAsync<T>(key: string) {
        const result: GetResult = await Storage.get({ key });
        return JSON.parse(result.value) as T;
    }

CodePudding user response:

Eager vs Lazy Execution

This happens because promises are eager and observable are lazy. That is, an observable doesn't do anything until subscribed to whereas a promise is going to attempt to resolve itself from the moment it is defined.

Here's how I would write it in order to resolve this issue in the most straightforward manner:

this.router.events.pipe(
  filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd),
  switchMap(_ => this.storageService.getAsync<IUser>('user'))
);

Update:

Of course, not only asynchronous code (like promises and observables) can have eager/lazy evaluation semantics. Regular synchronous code can have the same issues. Since it might be easier to understand the issue in a synchronous context, I'll create a few examples below that explore this without using promises or observables.


Consider this Non-Observable Code:

In this first example we have a function addNumbers that does the work of adding two numbers for you. It is eager, so it will do the work immediately and then return a value once invoked. You can see that on line 5, c() will be equal to 8. 3 and 5 are added together on line 6 then printed on line 8

function addNumbers(a, b){
  const answer = a   b;
  return () => answer;
}

const c = addNumbers(3, 5);

console.log("This should be an 8:", c());

In this next example, we have a very similar function, but it is lazy. It remembers the numbers you gave it but it doesn't actually add the numbers until something invokes it. In this case, c needs to be invoked c() before the 3 and the 5 are actually used in any way. 5 and 3 are not added together until line 7.

function addNumbers(a, b){
  return () => a   b;
}

const c = addNumbers(3, 5);

console.log("This should be an 8:", c());

Lazy vs Eager Consequences

Consider these two examples. If you understand why they print different values, you understand the issues at hand :)

Example 1:

const a = { n: 3 };
const b = { n: 5 };

function addNumbers(v1, v2) {
  const answer = { n: v1.n   v2.n };
  return () => answer;
}

const c = addNumbers(a, b);

a.n = 7;

console.log('Eager Evaluation:', c());

Here, c.n is equal to 3 5 because c was evaluated before a.n was set to 7. This is the same as when you retrieved the user before they were deleted. It doesn't see that the user info has changed in the meantime because the value has already been calculated.

Example 2:

const a = { n: 3 };
const b = { n: 5 };

function addNumbers(v1, v2) {
  return () => ({ n: v1.n   v2.n });
}

const c = addNumbers(a, b);

a.n = 7;

console.log('Lazy Evaluation:', c());

Here, c.n is equal to 7 5 because c was evaluated after a.n was set to 7. This is the same as when you retrieved the user using defer. This time we check the value of a.n the moment that c() is evaluated, not when addNumbers(v1, v2) is evaluated.

See the difference?

  • Related