web-dev-qa-db-fra.com

Comment passer des résultats entre observables chaînées

problème abstrait : chaque fois un émetteur et un événement observable source, une séquence d'appels d'API et Angular Services doit être déclenché. Certaines de ces invocations dépendent des résultats précédents. .

Dans mon exemple, la source observable startUpload$ Déclenche une série d'invocations.

Utiliser la déstructuration Ceci peut être écrit comme ceci:

this.startUploadEvent$.pipe(
      concatMap(event => this.getAuthenticationHeaders(event)),
      map(({ event, headers }) => this.generateUploadId(event, headers)),
      tap(({ event, headers, id }) => this.emitUploadStartEvent(id, event)),
      concatMap(({ event, headers, id }) => this.createPdfDocument(event, headers, id)),
      concatMap(({ event, headers, id, pdfId }) => this.uploadBilderForPdf(event, pdfId, headers, id)),
      mergeMap(({ event, headers, id, pdfId, cloudId }) => this.closePdf(cloudId, event, headers, id, pdfId)),
      tap(({ event, headers, id, pdfId, cloudId }) => this.emitUploadDoneEvent(id, event, cloudId)),
).subscribe()

Il lit presque comme une approche impérative. Mais cela a certains problèmes:

  • La chaîne de déstructuration est répétée sur le code et devient plus longue et plus longue { event, headers, id, pdfId, cloudId }
  • Méthodes (comme generateUploadId(event, headers)) sont nécessaires pour recevoir toutes les valeurs précédentes de manière à pouvoir les transmettre au tuyau suivant, même si la méthode elle-même n'exige pas
  • Les observables internes (dans les méthodes) sont nécessaires pour mapper les valeurs de sorte que d'autres étapes de tuyaux peuvent les détruire:

_

private closePdf(cloudId, event, headers, id, pdfId) {
    return this.httpClient.post(..., { headers } )
        .pipe(
             //...,
             map(() => ({ event, headers, id, pdfId, cloudId }))
        )
}

Ce serait bien si le compilateur pouvait prendre soin de la chaudière (comme avec async await) Pour écrire le code qui se lit comme celui-ci (sans aucun des problèmes mentionnés ci-dessus):

private startUpload(event: StartUploadEvent) {
    const headers = this.getAuthenticationHeaders(event)
    const id = this.generateUploadId()

    this.emitUploadStartEvent(id, event)

    const pdfId = this.createPdfDocument(event, headers, id)
    this.uploadBilderForPdf(event, pdfId, headers, id)

    const cloudId = this.closePdf(headers, pdfId)
    this.emitUploadDoneEvent(id, event, cloudId)

    return cloudId
  }

Comment passer des résultats entre observables chaînées sans les problèmes que j'ai mentionnés? Y a-t-il un concept RxJS que j'ai manqué?

28
d0x

Vous avez raison sur ces préoccupations et problèmes que vous avez mentionnés, mais le problème que je vois ici, vous transformez votre état d'esprit d'une approche impérative d'une approche réactive/fonctionnelle, mais examinons d'abord le code impératif en premier.

private startUpload(event: StartUploadEvent) {
    const headers = this.getAuthenticationHeaders(event)
    const id = this.generateUploadId()

    this.emitUploadStartEvent(id, event)

    const pdfId = this.createPdfDocument(event, headers, id)
    this.uploadBilderForPdf(event, pdfId, headers, id)

    const cloudId = this.closePdf(headers, pdfId)
    this.emitUploadDoneEvent(id, event, cloudId)

    return cloudId
}

Ici, vous voyez que le matériel est plus propre que vous avez event que vous pouvez passer et obtenir uniquement ce que vous voulez et transmettez-le aux fonctions suivantes et que nous souhaitons déplacer ce code à l'approche réactive/fonctionnelle.

le principal problème de mon point de vue est que vous avez fait perdre votre fonction le contexte qu'ils ont par exemple getAuthenticationHeaders ne devraient pas renvoyer le event du tout, il ne devrait que retourner headers et la même chose pour les autres fonctions.

lorsque vous traitez avec RXJS (AKA REACTIVE APPROCHE), vous avez un peu géré avec ces problèmes, ce qui est correct car il conserve les concepts fonctionnels appliqués et conserve votre code plus prévisible comme pure opérateurs ne devraient traiter que des données au même. Pipeline qui maintient tout ce qui maintient pure et ne conduit pas aux effets secondaires qui conduiront à un code imprévisible.

Je pense que voulez-vous être résolu avec nested pipes (c'est la meilleure solution de mon avis)

concatMap(event => this.getAuthenticationHeaders(event).pipe(
    map(headers => this.generateUploadId(event, headers).pipe())
))

et il est fortement utilisé dans certaines bibliothèques backend rxjs comme - marble.js

vous pouvez utiliser une approche similaire à Result Selector:

concatMap(event => this.getAuthenticationHeaders(event).pipe(
  map(headers => ({ headers, event }))
)),

ou les grandes autres solutions suggérées par lesquelles les gens le feront fonctionneront, mais vous aurez toujours les mêmes problèmes que vous mentionnez, mais avec un code plus propre/lisible.

Vous pouvez également le tourner en async/await Approche, mais vous perdrez la réactivité que RXJ vous fournit.

ce que je peux suggérer, c'est d'essayer de lire davantage sur la programmation réactive et de la façon dont vous déplacez votre état d'esprit et je vais fournir des liens ici que je vois est très bon de commencer et d'essayer certaines bibliothèques construites au sommet de RxJS Comme -- Cyclejs et je recommande de lire sur la programmation fonctionnelle qui aidera beaucoup aussi à ces bons livres Guide essentiellement adéquat sur FP (en JavaScript) & COMPOSANT LE LOGICIEL .

Je recommande cette excellente conversation recettes RxJS qui changera votre mode d'utilisation de RxJS.

Ressources utiles:

1
Shorbagy