javascript - promises - rxjs 6 documentation
Secuencia RxJS equivalente a promise.then()? (7)
Solía ​​desarrollar mucho con promesa y ahora me estoy mudando a RxJS. El documento de RxJS no proporciona un ejemplo muy claro sobre cómo pasar de la cadena de promesa a la secuencia del observador.
Por ejemplo, generalmente escribo promesa de cadena con múltiples pasos, como
// a function that returns a promise
getPromise()
.then(function(result) {
// do something
})
.then(function(result) {
// do something
})
.then(function(result) {
// do something
})
.catch(function(err) {
// handle error
});
¿Cómo debo reescribir esta cadena de promesa en el estilo RxJS?
Así es como lo hice.
Previamente
public fetchContacts(onCompleteFn: (response: gapi.client.Response<gapi.client.people.ListConnectionsResponse>) => void) {
const request = gapi.client.people.people.connections.list({
resourceName: ''people/me'',
pageSize: 100,
personFields: ''phoneNumbers,organizations,emailAddresses,names''
}).then(response => {
onCompleteFn(response as gapi.client.Response<gapi.client.people.ListConnectionsResponse>);
});
}
// caller:
this.gapi.fetchContacts((rsp: gapi.client.Response<gapi.client.people.ListConnectionsResponse>) => {
// handle rsp;
});
Después (ly?)
public fetchContacts(): Observable<gapi.client.Response<gapi.client.people.ListConnectionsResponse>> {
return from(
new Promise((resolve, reject) => {
gapi.client.people.people.connections.list({
resourceName: ''people/me'',
pageSize: 100,
personFields: ''phoneNumbers,organizations,emailAddresses,names''
}).then(result => {
resolve(result);
});
})
).pipe(map((result: gapi.client.Response<gapi.client.people.ListConnectionsResponse>) => {
return result; //map is not really required if you not changing anything in the response. you can just return the from() and caller would subscribe to it.
}));
}
// caller
this.gapi.fetchContacts().subscribe(((rsp: gapi.client.Response<gapi.client.people.ListConnectionsResponse>) => {
// handle rsp
}), (error) => {
// handle error
});
Para flujo de datos (equivalente a
then
):
Rx.Observable.fromPromise(...)
.flatMap(function(result) {
// do something
})
.flatMap(function(result) {
// do something
})
.subscribe(function onNext(result) {
// end of chain
}, function onError(error) {
// process the error
});
Una promesa se puede convertir en observable con
Rx.Observable.fromPromise
.
Algunos operadores prometen tener una traducción directa.
Por ejemplo,
RSVP.all
o
jQuery.when
puede ser reemplazado por
Rx.Observable.forkJoin
.
Tenga en cuenta que tiene un grupo de operadores que permite transformar datos de forma asincrónica y realizar tareas que no puede o sería muy difícil hacer con las promesas. Rxjs revela todos sus poderes con secuencias de datos asincrónicas (secuencia, es decir, más de 1 valor asincrónico).
Para la gestión de errores, el tema es un poco más complejo.
- hay operadores de catch y finally también
-
retryWhen
también puede ayudar a repetir una secuencia en caso de error -
También puede tratar los errores en el suscriptor con la función
onError
.
Para una semántica precisa, eche un vistazo más profundo a la documentación y ejemplos que puede encontrar en la web, o haga preguntas específicas aquí.
Este sería definitivamente un buen punto de partida para profundizar en la gestión de errores con Rxjs: https://xgrommx.github.io/rx-book/content/getting_started_with_rxjs/creating_and_querying_observable_sequences/error_handling.html
Por lo que acabo de descubrir, si devuelve un resultado en un flatMap, lo convierte en una matriz, incluso si devolvió una cadena.
Pero si devuelve un Observable, ese observable puede devolver una cadena;
Si entendí correctamente, te refieres a consumir los valores, en cuyo caso usas sbuscribe, es decir
const arrObservable = from([1,2,3,4,5,6,7,8]);
arrObservable.subscribe(number => console.log(num) );
Además, puede convertir lo observable en una promesa usando toPromise () como se muestra:
arrObservable.toPromise().then()
Si la función
getPromise
encuentra en el medio de una tubería de flujo, simplemente debe envolverla en una de las funciones
mergeMap
,
switchMap
o
concatMap
(generalmente
mergeMap
):
stream$.pipe(
mergeMap(data => getPromise(data)),
filter(...),
map(...)
).subscribe(...);
si desea iniciar su transmisión con
getPromise()
, envuélvala
from
función:
import {from} from ''rxjs'';
from(getPromise()).pipe(
filter(...)
map(...)
).subscribe(...);
Una alternativa más moderna:
import {from as fromPromise} from ''rxjs'';
import {catchError, flatMap} from ''rxjs/operators'';
fromPromise(...).pipe(
flatMap(result => {
// do something
}),
flatMap(result => {
// do something
}),
flatMap(result => {
// do something
}),
catchError(error => {
// handle error
})
)
También tenga en cuenta que para que todo esto funcione, debe
subscribe
a este
Observable
canalizado en alguna parte, pero supongo que se maneja en alguna otra parte de la aplicación.
Actualización de mayo de 2019, utilizando RxJs 6
De acuerdo con las respuestas proporcionadas anteriormente, desea agregar un ejemplo concreto con algunos datos de juguetes y promesas simples (con setTimeout) usando RxJs v6 para agregar claridad.
Simplemente actualice la identificación pasada (actualmente codificada como
1
) a algo que no existe para ejecutar la lógica de manejo de errores también.
Es importante destacar que también tenga en cuenta el uso
of
con el mensaje
catchError
.
import { from as fromPromise, of } from "rxjs";
import { catchError, flatMap, tap } from "rxjs/operators";
const posts = [
{ title: "I love JavaScript", author: "Wes Bos", id: 1 },
{ title: "CSS!", author: "Chris Coyier", id: 2 },
{ title: "Dev tools tricks", author: "Addy Osmani", id: 3 }
];
const authors = [
{ name: "Wes Bos", twitter: "@wesbos", bio: "Canadian Developer" },
{
name: "Chris Coyier",
twitter: "@chriscoyier",
bio: "CSS Tricks and CodePen"
},
{ name: "Addy Osmani", twitter: "@addyosmani", bio: "Googler" }
];
function getPostById(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const post = posts.find(post => post.id === id);
if (post) {
console.log("ok, post found!");
resolve(post);
} else {
reject(Error("Post not found!"));
}
}, 200);
});
}
function hydrateAuthor(post) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const authorDetails = authors.find(person => person.name === post.author);
if (authorDetails) {
post.author = authorDetails;
console.log("ok, post hydrated with author info");
resolve(post);
} else {
reject(Error("Author not Found!"));
}
}, 200);
});
}
function dehydratePostTitle(post) {
return new Promise((resolve, reject) => {
setTimeout(() => {
delete post.title;
console.log("ok, applied transformation to remove title");
resolve(post);
}, 200);
});
}
// ok, here is how it looks regarding this question..
let source$ = fromPromise(getPostById(1)).pipe(
flatMap(post => {
return hydrateAuthor(post);
}),
flatMap(post => {
return dehydratePostTitle(post);
}),
catchError(error => of(`Caught error: ${error}`))
);
source$.subscribe(console.log);
Datos resultantes:
ok, post found!
ok, post hydrated with author info
ok, applied transformation to remove title
{ author:
{ name: ''Wes Bos'',
twitter: ''@wesbos'',
bio: ''Canadian Developer'' },
id: 1 }
La parte clave es equivalente a lo siguiente usando un flujo de control de promesa simple:
getPostById(1)
.then(post => {
return hydrateAuthor(post);
})
.then(post => {
return dehydratePostTitle(post);
})
.then(author => {
console.log(author);
})
.catch(err => {
console.error(err);
});