web-dev-qa-db-fra.com

Attendre une opération asynchrone dans onNext de RxJS Observable

J'ai une séquence RxJS en cours de consommation normale ...

Cependant, dans le gestionnaire observable 'onNext', certaines opérations se terminent de manière synchrone, mais d'autres nécessitent des rappels asynchrones, qu'il faut attendre avant de traiter l'élément suivant dans la séquence d'entrée.

... peu confus comment faire cela. Des idées? Merci!

someObservable.subscribe(
    function onNext(item)
    {
        if (item == 'do-something-async-and-wait-for-completion')
        {
            setTimeout(
                function()
                {
                    console.log('okay, we can continue');
                }
                , 5000
            );
        }
        else
        {
            // do something synchronously and keep on going immediately
            console.log('ready to go!!!');
        }
    },
    function onError(error)
    {
        console.log('error');
    },
    function onComplete()
    {
        console.log('complete');
    }
);
26
user3291110

Chaque opération que vous souhaitez effectuer peut être modélisée comme un observable. Même le fonctionnement synchrone peut être modélisé de cette façon. Ensuite, vous pouvez utiliser map pour convertir votre séquence en séquence de séquences, puis utiliser concatAll pour aplatir la séquence.

someObservable
    .map(function (item) {
        if (item === "do-something-async") {
            // create an Observable that will do the async action when it is subscribed
            // return Rx.Observable.timer(5000);

            // or maybe an ajax call?  Use `defer` so that the call does not
            // start until concatAll() actually subscribes.
            return Rx.Observable.defer(function () { return Rx.Observable.ajaxAsObservable(...); });
        }
        else {
            // do something synchronous but model it as an async operation (using Observable.return)
            // Use defer so that the sync operation is not carried out until
            // concatAll() reaches this item.
            return Rx.Observable.defer(function () {
                return Rx.Observable.return(someSyncAction(item));
            });
        }
    })
    .concatAll() // consume each inner observable in sequence
    .subscribe(function (result) {
    }, function (error) {
        console.log("error", error);
    }, function () {
        console.log("complete");
    });

Pour répondre à certains de vos commentaires ... vous devez à un moment donné imposer certaines attentes au flux de fonctions. Dans la plupart des langages, lorsqu'il s'agit de fonctions pouvant être asynchrones, les signatures de fonction sont asynchrones et la nature asynchrone vs sync réelle de la fonction est masquée en tant que détail d'implémentation de la fonction. Ceci est vrai que vous utilisiez des promesses javaScript, des observables Rx, des tâches c #, des contrats c ++ Futures, etc. Les fonctions finissent par retourner une promesse/observable/tâche/future/etc et si la fonction est réellement synchrone, l'objet renvoyé est juste déjà terminé.

Cela dit, puisqu'il s'agit de JavaScript, vous pouvez pouvez tricher:

var makeObservable = function (func) {
    return Rx.Observable.defer(function () {
        // execute the function and then examine the returned value.
        // if the returned value is *not* an Rx.Observable, then
        // wrap it using Observable.return
        var result = func();
        return result instanceof Rx.Observable ? result: Rx.Observable.return(result);
    });
}

someObservable
    .map(makeObservable)
    .concatAll()
    .subscribe(function (result) {
    }, function (error) {
        console.log("error", error);
    }, function () {
        console.log("complete");
    });
24
Brandon

Tout d’abord, déplacez vos opérations asynchrones de subscribe, elles ne sont pas conçues pour les opérations asynchrones.

Ce que vous pouvez utiliser est mergeMap (alias flatMap) ou concatMap . Je mentionne les deux, parce que concatMap est en fait mergeMap avec le paramètre concurrent défini sur 1. Cela est utile, car vous voudriez parfois limiter le nombre de requêtes simultanées, tout en exécutant un couple simultané.

source.concatMap(item => {
  if (item == 'do-something-async-and-wait-for-completion') {
    return Rx.Observable.timer(5000)
      .mapTo(item)
      .do(e => console.log('okay, we can continue'));
    } else {
      // do something synchronously and keep on going immediately
      return Rx.Observable.of(item)
        .do(e => console.log('ready to go!!!'));
    }
}).subscribe();

Je vais également montrer comment vous pouvez évaluer limiter vos appels. Conseil: _ Limitez uniquement le taux au moment où vous en avez réellement besoin, comme lorsque vous appelez une API externe qui autorise uniquement un certain nombre de demandes par seconde ou par minute. Sinon, il est préférable de limiter le nombre d'opérations simultanées et de laisser le système se déplacer à une vitesse maximale.

Nous commençons par l'extrait suivant:

const concurrent;
const delay;
source.mergeMap(item =>
  selector(item, delay)
, concurrent)

Ensuite, nous devons choisir les valeurs pour concurrent, delay et mettre en œuvre selector. concurrent et delay sont étroitement liés. Par exemple, si nous voulons exécuter 10 éléments par seconde, nous pouvons utiliser concurrent = 10 et delay = 1000 (milliseconde), mais aussi concurrent = 5 et delay = 500 ou concurrent = 4 et delay = 400. Le nombre d'éléments par seconde sera toujours concurrent / (delay / 1000).

Maintenant, implémentons selector. Nous avons deux options. Nous pouvons définir un temps d’exécution minimal pour selector, nous pouvons lui ajouter un délai constant, nous pouvons émettre les résultats dès qu’ils sont disponibles, nous ne pouvons émettre le résultat que lorsque le délai minimal est passé, etc. C'est même possible d'ajouter un délai d'attente à l'aide de l'opérateur timeout . Commodité.

Régler le temps minimum, envoyer le résultat tôt:

function selector(item, delay) {
   return Rx.Observable.of(item)
     .delay(1000) // replace this with your actual call.
     .merge(Rx.Observable.timer(delay).ignoreElements())
}

Régler le temps minimum, envoyer le résultat en retard:

function selector(item, delay) {
   return Rx.Observable.of(item)
     .delay(1000) // replace this with your actual call.
     .Zip(Rx.Observable.timer(delay), (item, _))
}

Ajouter du temps, envoyer le résultat plus tôt:

function selector(item, delay) {
   return Rx.Observable.of(item)
     .delay(1000) // replace this with your actual call.
     .concat(Rx.Observable.timer(delay).ignoreElements())
}

Ajouter du temps, envoyer le résultat en retard:

function selector(item, delay) {
   return Rx.Observable.of(item)
     .delay(1000) // replace this with your actual call.
     .delay(delay)
}
4
Dorus

Un autre exemple simple pour effectuer des opérations asynchrones manuelles.

Sachez que ce n'est pas une bonne pratique réactive! Si vous voulez seulement attendre 1000 ms, utilisez Rx.Observable.timer ou l'opérateur delay.

someObservable.flatMap(response => {
  return Rx.Observable.create(observer => {
    setTimeout(() => {
      observer.next('the returned value')
      observer.complete()
    }, 1000)
  })
}).subscribe()

Maintenant, remplacez setTimeout par votre fonction asynchrone, comme Image.onload ou fileReader.onload ...

0
TeChn4K