J'avais l'habitude de développer beaucoup avec des promesses et maintenant je passe à RxJS. La documentation de RxJS ne fournit pas un exemple très clair sur la façon de passer d'une chaîne de promesse à une séquence d'observateur.
Par exemple, j’écris habituellement une chaîne de promesses en plusieurs étapes, comme
// 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
});
Comment dois-je réécrire cette chaîne de promesses dans le style RxJS?
Pour le flux de données (équivalent à 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
});
Une promesse peut être convertie en observable avec Rx.Observable.fromPromise
.
Certains opérateurs de promesse ont une traduction directe. Par exemple, RSVP.all
ou jQuery.when
peut être remplacé par Rx.Observable.forkJoin
.
N'oubliez pas que vous disposez d'un groupe d'opérateurs qui vous permettent de transformer des données de manière asynchrone et d'effectuer des tâches que vous ne pouvez pas ou qu'il serait très difficile de faire avec des promesses. Rxjs révèle toutes ses puissances avec des séquences de données asynchrones (séquence c’est-à-dire plus d’une valeur asynchrone).
Pour la gestion des erreurs, le sujet est un peu plus complexe.
retryWhen
peut également aider à répéter une séquence en cas d'erreuronError
.Pour une sémantique précise, examinez de plus près la documentation et les exemples que vous pouvez trouver sur le Web ou posez des questions spécifiques ici.
Ce serait certainement un bon point de départ pour approfondir la gestion des erreurs avec Rxjs: https://xgrommx.github.io/rx-book/content/getting_started_with_rxjs/creating_and_querying_observable_sequences/error_handling.html
Une alternative plus moderne:
import {fromPromise} from 'rxjs/observable/fromPromise';
import {catchError, flatMap} from 'rxjs/operators';
fromPromise(...).pipe(
flatMap(result => {
// do something
}),
flatMap(result => {
// do something
}),
flatMap(result => {
// do something
}),
catchError(error => {
// handle error
})
)
Mise à jour de mai 2019, à l'aide de RxJs 6
En accord avec les réponses fournies ci-dessus, a souhaité ajouter un exemple concret avec des données de jouets et de simples promesses (avec setTimeout) en utilisant RxJs v6 pour plus de clarté.
Il suffit de mettre à jour l'ID transmis (actuellement codé en dur en tant que 1
) sur quelque chose qui n'existe pas pour exécuter également la logique de traitement des erreurs. Il est important de noter également l'utilisation de of
avec le message 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);
Des données de sortie:
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 partie clé est équivalente à la suivante en utilisant un flux de contrôle de promesse simple:
getPostById(1)
.then(post => {
return hydrateAuthor(post);
})
.then(post => {
return dehydratePostTitle(post);
})
.then(author => {
console.log(author);
})
.catch(err => {
console.error(err);
});
Autant que je viens de le savoir, si vous retournez un résultat dans un flatMap, il le convertit en tableau, même si vous avez renvoyé une chaîne.
Mais si vous retournez un observable, cet observable peut renvoyer une chaîne;
si la fonction getPromise
se trouve au milieu d'un tuyau de flux, vous devez simplement l'envelopper dans l'une des fonctions mergeMap
, switchMap
ou concatMap
(généralement mergeMap
):
stream$.pipe(
mergeMap(data => getPromise(data)),
filter(...),
map(...)
).subscribe(...);
si vous voulez démarrer votre flux avec getPromise()
, enveloppez-le dans la fonction from
:
import {from} from 'rxjs';
from(getPromise()).pipe(
filter(...)
map(...)
).subscribe(...);
Si j'ai bien compris, vous voulez dire consommer les valeurs, auquel cas vous utilisez sbuscribe i.e.
const arrObservable = from([1,2,3,4,5,6,7,8]);
arrObservable.subscribe(number => console.log(num) );
De plus, vous pouvez simplement transformer l'observable en promesse en utilisant toPromise () comme indiqué:
arrObservable.toPromise().then()
Voici comment je l'ai fait.
précédemment
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;
});
After (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
});