Tengo una aplicación móvil que estoy creando y ahora mismo estoy trabajando en la autenticación. Antes de llegar a mi página de inicio, necesito acceder a una variedad de puntos finales en una API que he creado antes de poder mostrar datos al usuario.
Todos los puntos finales devuelven los datos correctos cuando se prueban en Postman, sin embargo, obtengo un valor null
en mi segunda llamada asincrónica cuando lo uso en mi aplicación.
Estoy seguro de que tiene algo que ver con el orden en que se realizan estas llamadas, así que solo estaba buscando ayuda sobre cómo puedo esperar correctamente a que termine una llamada antes de comenzar otra.
public login() { this.showLoading(); this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS .subscribe( res => { console.log(res); localStorage.setItem("UserId", res.toString()); }, err => { console.log(err); }); this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL .subscribe( res => { console.log(res); localStorage.setItem("EmployeeId", res.toString()); }, err => { console.log(err); }); this.authService.login(this.registerCredentials) .subscribe( data => { this.loading.dismissAll(); console.log('User logged in successfully! ', data); this.nav.push(TabsPage); localStorage.setItem("Username", this.registerCredentials.username); localStorage.setItem("isLoggedIn", "true"); }, error => { this.loading.dismissAll(); this.showAlert("Uh oh!", "Something went wrong. Please re-enter your login credentials or check your connection."); console.log(error); }); }
Su código original tiene un error que conduce a este error. Tiene tres llamadas en su código que llamaré A), B) y C):
A) this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS B) this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL C) this.authService.login(this.registerCredentials)
Lo que debe comprender sobre RXJS es la diferencia entre un Observable frío (que representa toda la información necesaria para iniciar una operación asíncrona) y un Observable activo (que es un Observable con la operación asíncrona ya iniciada ).
Las tres llamadas A), B) y C) simplemente crean observables fríos que se inician en el momento en que llamas a .subscribe()
en ellos. Entonces, para el momento en que se construye B), A) ya se inició pero aún no se ha completado. Entonces, la llamada a localStorage.getItem("UserId")
devolverá un valor nulo, porque A) aún no ha invocado la next
devolución de llamada de su suscriptor.
Entonces, lo que quieres hacer es que B) espere a A). Además, en lugar de meter algo en estado global (almacenamiento local), probablemente sea mejor hacer fluir el resultado de A) a B). Introduzca el operador .mergeMap()
:
this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS .map(res => res.toString()) .do(userId => localStorage.setItem("UserId", userId)) // cleanly separate side-effects into .do() calls .mergeMap(userId => this.userService.getEmployeeIdFromUserId(userId)) .map(res => res.toString()) .do(employeeId => localStorage.setItem("EmployeeId", employeeId)) .subscribe( employeeId => { console.log(employeeId); }, err => { console.log(err); });
Lo bueno de rxjs es que el manejo de errores simplemente funciona a lo largo de su cadena Observable. Si puede ejecutar C) en paralelo, eche un vistazo a .forkJoin()
.
Finalmente, si necesita una explicación práctica de .mergeMap()
, eche un vistazo a esta respuesta: SwitchMap vs MergeMap en el ejemplo #ngrx
Esto debería funcionar. No olvide import 'rxjs/Rx'
this.userService.getUserIdFromUserName(this.registerCredentials.username) .map(res => res.toString()) .do(userId => { console.log(res); localStorage.setItem("UserId", userId); }) .flatMap(userId => { return this.userService.getEmployeeIdFromUserId(userId); }) .do(res => { console.log(res); localStorage.setItem("EmployeeId", res.toString()); })