Послідовність RxJS еквівалентна promis.then ()?


83

Раніше я багато розвивав багатообіцяюче, а зараз переходжу до RxJS. Документ RxJS не дає дуже чіткого прикладу того, як перейти від ланцюжка обіцянок до послідовності спостерігачів.

Наприклад, я зазвичай пишу ланцюжок обіцянок із декількома кроками, наприклад

// 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
});

Як мені переписати цей ланцюжок обіцянок у стилі RxJS?

Відповіді:


80

Для потоку даних (еквівалентно 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
  });

Обіцянку можна перетворити на спостережливу за допомогою Rx.Observable.fromPromise.

Деякі оператори обіцянок мають прямий переклад. Наприклад RSVP.all, або jQuery.whenможе бути замінений на Rx.Observable.forkJoin.

Майте на увазі, що у вас є купа операторів, які дозволяють трансформувати дані асинхронно та виконувати завдання, які ви не можете зробити або які важко зробити за допомогою обіцянок. Rxjs розкриває всі свої повноваження за допомогою асинхронних послідовностей даних (послідовність, тобто більше 1 асинхронного значення).

Що стосується управління помилками, тема трохи складніша.

  • є улов і, нарешті, оператори теж
  • retryWhen також може допомогти повторити послідовність у випадку помилки
  • Ви також можете мати справу з помилками самого абонента за допомогою onErrorфункції.

Для точної семантики глибше ознайомтеся з документацією та прикладами, які ви можете знайти в Інтернеті, або задайте конкретні запитання тут.

Це, безумовно, було б гарною відправною точкою для глибшого управління помилками за допомогою Rxjs: https://xgrommx.github.io/rx-book/content/getting_started_with_rxjs/creating_and_querying_observable_sequences/error_handling.html


Я завжди бачу, що спостережувана послідовність закінчується Subscribe (). Оскільки це лише функція спостережуваного об'єкта, чи є для цього підстави? Це функція для запуску послідовності?
Haoliang Yu

саме так. Якщо спостерігачів не буде пройдено через підписку, ваш спостережуваний не видаватиме жодних даних, тому ви не побачите жодного потоку даних.
user3743222

7
Я рекомендую вам поглянути на це: gist.github.com/staltz/868e7e9bc2a7b8c1f754 . ЦЕ може бути більш смачним, ніж офіційний документ.
user3743222

3
Promise.thenшвидше .flatMapніж .map.
Тамаш Гегедус

1
FYI це не зовсім еквівалентно, оскільки у Promiseверсії помилки з 3-ї thenбудуть вловлювані catch. Тут їх немає.
mik01aj

35

Більш сучасна альтернатива:

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

Також зауважте, що для того, щоб все це працювало, вам потрібно десь subscribeпрокласти цю трубу Observable, але я припускаю, що це обробляється в іншій частині програми.


Я дуже новачок у RxJS, але враховуючи те, що тут ми маємо справу лише з початковим потоком однієї події, і mergeMap()тому насправді немає чого об’єднувати , я вважаю, що в цьому випадку ми могли б досягти точно того ж concatMap()або switchMap(). Чи правильно я зрозумів ...?
Ден Кінг

8

Оновіть травень 2019 року, використовуючи RxJs 6

Погодьтесь із наведеними вище відповідями, хотіли додати конкретний приклад із деякими даними про іграшки та простими обіцянками (із setTimeout), використовуючи RxJs v6 для додання ясності.

Просто оновіть переданий ідентифікатор (в даний час з кодом як 1) до чогось, що не існує, щоб також виконати логіку обробки помилок. Важливо також звернути увагу на використання ofз 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);

Вихідні дані:

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 }

Ключова частина еквівалентна наступній, використовуючи звичайний потік управління потоком:

getPostById(1)
  .then(post => {
    return hydrateAuthor(post);
  })
  .then(post => {
    return dehydratePostTitle(post);
  })
  .then(author => {
    console.log(author);
  })
  .catch(err => {
    console.error(err);
  });

0

якщо getPromiseфункція знаходиться посередині потокової труби, вам слід просто обернути її в одну з функцій mergeMap, switchMapабо concatMap(зазвичай mergeMap):

stream$.pipe(
   mergeMap(data => getPromise(data)),
   filter(...),
   map(...)
 ).subscribe(...);

якщо ви хочете почати свій потік, getPromise()потім оберніть його у fromфункцію:

import {from} from 'rxjs';

from(getPromise()).pipe(
   filter(...)
   map(...)
).subscribe(...);

0

Наскільки я щойно дізнався, якщо ви повертаєте результат у flatMap, він перетворює його в масив, навіть якщо ви повернули рядок.

Але якщо ви повертаєте Observable, це Observable може повернути рядок;


0

Якщо я правильно зрозумів, ви маєте на увазі споживання значень, і в цьому випадку ви використовуєте sbuscribe, тобто

const arrObservable = from([1,2,3,4,5,6,7,8]);
arrObservable.subscribe(number => console.log(num) );

Крім того, ви можете просто перетворити спостережуване на обіцянку за допомогою toPromise (), як показано:

arrObservable.toPromise().then()

0

Ось як я це зробив.

Раніше

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

Після (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

Послідовність RxJS еквівалентна promis.then ()?

Наприклад

function getdata1 (argument) {
        return this.http.get(url)
            .map((res: Response) => res.json());
    }

    function getdata2 (argument) {
        return this.http.get(url)
            .map((res: Response) => res.json());
    }

    getdata1.subscribe((data1: any) => {
        console.log("got data one. get data 2 now");
        getdata2.subscribe((data2: any) => {
            console.log("got data one and two here");
        });
    });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.