2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【RxJS】firstValueFromって何?Observableから値を1つだけ取り出す関数を調べてみた

2
Posted at

はじめに

RxJSのコードを読んでいたら、こんな書き方が出てきました。

const value = await firstValueFrom(source$);

Observableをawaitしているように見えて、最初は戸惑いました。Observableはsubscribeして使うものだと思っていたので、firstValueFromが何をしているのか気になって調べてみました。

firstValueFromは何をしているのか

firstValueFromは、Observableから最初に流れてきた値を1つだけ取り出して、Promiseとして返す関数です。

import { firstValueFrom } from 'rxjs';

const value = await firstValueFrom(source$);

RxJSのObservableは本来subscribeして使うものですが、値を1回だけ取りたいだけの場面でsubscribe/unsubscribeを手で書くのは面倒です。firstValueFromを使うと、その面倒な部分を関数に任せて、ObservableをawaitできるPromiseとして扱えます。

やっていることをイメージで表すと、次のコードに近いです。

import { first } from 'rxjs';

const value = await new Promise((resolve, reject) => {
  source$.pipe(first()).subscribe({
    next: resolve,
    error: reject,
  });
});

first()で最初の値だけに絞り込み、値が流れてきた時点でPromiseをresolveしています。これを毎回手書きするのは大変なので、firstValueFromはありがたい存在だと感じました。

値を出さずに完了した場合はエラーになる

Observableが何も値を発行せずに完了(complete)した場合、firstValueFromが返すPromiseはEmptyErrorでrejectされます(awaitすると例外として投げられます)。

値を出さない可能性があるObservableに使う場合は、defaultValueオプションでデフォルト値を指定できます。

const value = await firstValueFrom(source$, { defaultValue: null });

これを指定しておけば、Observableが空のまま完了してもエラーにならず、代わりにnull(指定した値)が返ってきます。

lastValueFromとの使い分け

似た関数にlastValueFromがあります。両者の違いは、どのタイミングで、どの値をPromiseの結果にするかです。

const source$ = of(1, 2, 3); // 同期的に1, 2, 3を流してcomplete

const first = await firstValueFrom(source$); // → 1
const last = await lastValueFrom(source$);   // → 3
  • firstValueFrom … 最初に流れてきた値を受け取ったら、そこでunsubscribeする
  • lastValueFrom … Observableが完了するまで待ち、最後に流れてきた値を受け取る

この違いから、使いどころも変わってきます。

  • HTTPリクエストのように、1回だけ値が流れてすぐcompleteするものにはfirstValueFrom
  • 複数回値が流れるObservableから、途中経過は不要で最終結果だけ欲しいときはlastValueFrom

注意が必要なのは、lastValueFromは「最後の値」を知るためにObservableが完了するまで待つという点です。interval()のように値を流し続けて完了しないObservableにlastValueFromを使うと、いつまでもPromiseがresolveされません。

一方firstValueFromは最初の値が来た時点でunsubscribeするので、interval()のように最初の値が必ず発行されるObservableであれば、完了しないストリームにも使えます。ただし、値を一度も発行せず完了もしないObservable(NEVERや、値を流さないSubjectなど)に使った場合は、こちらもPromiseが未解決のまま残ります。

toPromise()との違い

以前はObservableが持つtoPromise()というメソッドがよく使われていましたが、現在は非推奨とされていて、firstValueFrom・lastValueFromへの置き換えが案内されています。

toPromise()には「Observableが何も値を流さずに完了した場合、undefinedを返す」という挙動があり、これが紛らわしいとされていました。値を取得できなかったのか、Observableがundefinedという値を流したのか、戻り値だけでは区別がつきません。

firstValueFrom・lastValueFromはこの点が改善されていて、defaultValueを指定していない場合、値を出さずに完了するとPromiseがEmptyErrorでrejectされる(awaitすると例外として投げられる)ので、値が取れなかったことにその場で気づけます。defaultValueを指定した場合は、代わりにその値でresolveされます。

もう一点、toPromise()は完了時点の最後の値でresolveする関数だったので、従来と同じ挙動を保ちたい場合はfirstValueFromではなくlastValueFromに置き換えるのが対応関係としては近くなります。最初の値だけが必要な場合にfirstValueFromを選ぶ、という使い分けです。

まとめ

  • firstValueFromはObservableから最初の値を1つだけ取り出し、Promiseとして返す関数
  • 値を出さずに完了するとPromiseがEmptyErrorでrejectされる。可能性がある場合はdefaultValueオプションで対処する
  • lastValueFromは最後の値を返すために完了を待つ。完了もエラーもしないObservableではPromiseが未解決のまま残るので、firstValueFromとは使い分けが必要
  • 非推奨になったtoPromise()の代わりとして案内されている

subscribeしか知らなかったので、Observableを1回だけの値としてPromise化できる関数があるのは便利だと感じました。lastValueFromとの違いは「完了を待つかどうか」で整理すると迷わずに済みそうです。

参考になったら いいね や ストック をお願いします!
同じような経験をされた方のコメントもお待ちしています。

参考

関連リンク

技術ブログでも学びや検証内容をまとめています。

nakamuuublog

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?