Tengo un observable que escucha los cambios de ruta :) También tengo una promise que borra mi almacenamiento local y cuando termina, cambio inmediatamente la ruta, pero mi switchMap/switchMapTo dentro de mi observable de cambio de ruta obtiene el valor anterior... ¿por qué sucede esto? ¿suceder?
He intentado muchas formas de resolver esto: encontré dos que funcionan :) Pero deseo entender por qué funcionaron. ¿Alguien podría ayudar? ¿Es esto algo relacionado con problemas hot/cold observables ? ¿Quizás algo con el event loop ? Realmente no lo sé :)
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; }Esto sucede porque las promesas son ansiosas y las observables son perezosas. Es decir, un observable no hace nada hasta que se suscribe, mientras que una promesa intentará resolverse a sí misma desde el momento en que se define.
Así es como lo escribiría para resolver este problema de la manera más directa:
this.router.events.pipe( filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd), switchMap(_ => this.storageService.getAsync<IUser>('user')) );Actualizar:
Por supuesto, no solo el código asíncrono (como las promesas y los observables) puede tener una semántica de evaluación ansiosa/perezosa. El código síncrono regular puede tener los mismos problemas. Dado que podría ser más fácil comprender el problema en un contexto síncrono, crearé algunos ejemplos a continuación que exploran esto sin usar promesas u observables.
En este primer ejemplo tenemos una función addNumbers que hace el trabajo de sumar dos números por ti. Está ansioso, por lo que hará el trabajo de inmediato y luego devolverá un valor una vez invocado. Puede ver que en la línea 5, c() será igual a 8. 3 y 5 se suman en la línea 6 y luego se imprimen en la línea 8
function addNumbers(a, b){ const answer = a + b; return () => answer; } const c = addNumbers(3, 5); console.log("This should be an 8:", c()); En el siguiente ejemplo, tenemos una función muy similar, pero es perezosa. Recuerda los números que le diste pero en realidad no suma los números hasta que algo lo invoca. En este caso, c debe invocarse c() antes de que el 3 y el 5 se usen de alguna manera. 5 y 3 no se suman hasta la línea 7.
function addNumbers(a, b){ return () => a + b; } const c = addNumbers(3, 5); console.log("This should be an 8:", c());Considere estos dos ejemplos. Si comprende por qué imprimen valores diferentes, comprende los problemas en cuestión :)
Ejemplo 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); an = 7; console.log('Eager Evaluation:', c()); Aquí, cn es igual a 3 + 5 porque c se evaluó antes de que an se estableciera en 7 . Esto es lo mismo que cuando recuperó al usuario antes de que se eliminara. Mientras tanto, no ve que la información del usuario haya cambiado porque el valor ya se calculó.
Ejemplo 2:
const a = { n: 3 }; const b = { n: 5 }; function addNumbers(v1, v2) { return () => ({ n: v1.n + v2.n }); } const c = addNumbers(a, b); an = 7; console.log('Lazy Evaluation:', c()); Aquí, cn es igual a 7 + 5 porque c se evaluó después de que an se estableciera en 7 . Esto es lo mismo que cuando recuperó al usuario usando defer . Esta vez comprobamos el valor de an en el momento en que se evalúa c() , no cuando se evalúa addNumbers(v1, v2) .
¿Ver la diferencia?
Alguna explicación que me ayudó un par de veces a entender defer .
La composición de funciones se ejecuta de interior a exterior. Primero los argumentos internos dados a la función externa, luego la función externa.
function f(x) { const result = x * 10; console.log("f: ", x, " -> ", result); return result; } function g(x) { const result = x + 1; console.log("g: ", x, " -> ", result); return result; } cosnole.log(f(g(2))) // g: 2 => 3 // f: 3 => 30 // 30Las promesas están ansiosas. No esperan a que alguien se suscriba.
new Promise(() => console.log("1")); console.log("2"); 1 2 function f(p) { console.log('f:'); } f(new Promise(() => console.log("1)); // 1 // f: Así es el caso de from(somePromise) :
from(new Promise((resolve) => { console.log("promise 1"); setTimeout(() => { console.log("promise 2"); resolve(5); }); }); console.log("subscribe - before"); from.pipe(tap(x) => { console.log("tap: ", x); }).subscribe(); console.log("subscribe - after"); // promise 1 // subscribe - before // subscribe - after // promise 2 // tap: 5 Qué hace detrás from escena (simplificado):
function from(promise) { return new Observable(subscriber => { promise.then(result => { subscriber.next(result); subscriber.complete(); }, (err) => subscriber.error(err)); }); } from devuelve un observable que emite cuando se emite la promesa, pero la promesa en sí ya comenzó a funcionar incluso antes de que from comenzara y creara un observable.
El operador defer es para esos casos. Cuando se desea retrasar el trabajo y solo ejecutarlo cuando alguien se suscribe.
Lo que defer hace detrás de escena (versión simplificada):
function defer(workFunction) { return new Observable(subscriber => { from(workFunction()).subscribe((result) => { subscriber.next(result); }); }); }Solo cuando alguien se suscribe, se activa la función de trabajo.
Si damos defer a switchMapTo , switchMapTo se suscribirá solo cuando su fuente emita.
Qué hace switchMapTo detrás de escena simplificado:
function switchMapTo(workObservable) { let lastSubs; return new Observable(subscriber => { if (lastSubs) { lastSubs.unsubscribe(); } lastSubs = workObservable.pipe( tap(result => subscriber.next(result)), ).subscribe(); }); }