web-dev-qa-db-fra.com

Séquence RxJS équivalente à promise.then ()?

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?

60
Haoliang Yu

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.

  • il y a catch et enfin opérateurs aussi
  • retryWhen peut également aider à répéter une séquence en cas d'erreur
  • vous pouvez également gérer les erreurs de l'abonné lui-même avec la fonction onError.

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

66
user3743222

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
   })
)
11
mik01aj

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);
  });
1
arcseldon

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;

0
Samantha Adrichem

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(...);
0
Maksim Romanenko

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()
0
David Kabii

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
});
0
Anand Rockzz