🦔

【Angular】FirstValuefromの特徴

2022/05/19に公開

こんにちは。
フロントエンドエンジニアとしてAngularをメインにしているマサキです。
今回はFirstValuefromについて記事にしていきたいと思います。

FirstValuefromとは

FirstValuefromはRxJSのオペレーターです。
observableをサブスクライブして渡ってきた最初の値をObservableからPromiseに変換し、そのsubscriptionを自動的に終了させることが出来ます。


■ 構文

firstValueFrom<T, D>(source: Observable<T>, config?:FirstValueFromConfig<D>): Promise<T | D>

■ FirstValuefromの公式ドキュメント
https://rxjs.dev/api/index/function/firstValueFrom



これまでObservableからPromiseに変換する場合は、toPromiseを使用していましたが、ForstValueformはtoPromise×firstオペレーター×unsebscribeを一気に実行してくれるので、かなり便利なオペレーターかと思います。

また下記記事にもあるように、toPromiseは非推奨となりForstValueformを使用して下さいとのことなので、非同期処理を同期させる為に使用する機会が増えてくるのかと思います。

https://indepth.dev/posts/1287/rxjs-heads-up-topromise-is-being-deprecated

toPromiseが廃止の理由

toPromiseが廃止される理由は、Observable prototypeを統一するためとtoPromiseの場合、undefinedの挙動が不完全であり、使用する機会が限られてしまう為です。

import { EMPTY, of } from 'rxjs';

of(1, 2, 3, undefined).toPromise().then((value) => {
    console.log(value) // undefined
  });

EMPTY.toPromise().then((value) => {
    console.log(value) // undefined
  });

この場合、両方ともundefinedが返ってきます。

import { EMPTY, of } from 'rxjs';

of(1, 2, undefined, 3).toPromise().then((value) => {
    console.log(value); // 3
  });

EMPTY.toPromise().then((value) => {
  console.log(value); // undefined
});

しかし、undefinedと3の順番を入れ替えた場合は3が返ってきます。値の順序によって最後の値をObservableからPromiseに変換するlastValueFromオペレータのような挙動になってしまいます。


FirstValuefromの挙動

FirstValuefromの特徴は先ほども述べた通り、最初の値(first value)を取得してObservableをPromiseに変換します。

import { interval, take, firstValueFrom } from 'rxjs';

async function execute() {
  const source$ = interval(10).pipe(take(10));
  const finalNumber = await firstValueFrom(source$);
  console.log(`The final number is ${finalNumber}`); 
  // The final number is 0
}
execute();

最初の値である0をキャッチしてくれます。
ちなみに、lastValueFromオペレーターを使用した場合は、最後の値である9を取得することが出来ます。

import { interval, take, lastValueFrom } from 'rxjs';

async function execute() {
  const source$ = interval(10).pipe(take(10));
  const finalNumber = await lastValueFrom(source$);
  console.log(`The final number is ${finalNumber}`);
  // The final number is 9
}
execute();


EmptyError

値をキャッチする前にストリームが完了した場合、EmptyErrorが返ってきます。

If the observable stream completes before any values were emitted, the returned
promise will reject with EmptyError or will resolve with the default value if a default was specified.

import { interval, take, firstValueFrom } from 'rxjs';

async function execute() {
  const source$ = interval(10).pipe(take(0));
  const finalNumber = await firstValueFrom(source$)
    .then((value) => {
      console.log(`value:`, value);
    })
    .catch((error) => {
      console.log(`error:`, error);
    });
  console.log(`The final number is ${finalNumber}`);
}
execute();



コンソールにEmptyErrorが表示され、値もundefinedが返ってきます。

error:
EmptyErrorImpl {
	stack: "Error at _super 
	, name: "EmptyError",
	message: "no elements in sequence"
	}
The final number is undefined


catch、finally

Promiseに変換される為、catchやfinallyも使用することが出来ます。mapオペレーターでthrow new Errorを記載すると、エラーをcatchすることが出来ます。

import { interval, take, firstValueFrom, map } from 'rxjs';

async function execute() {
  const source$ = interval(1000).pipe(
    take(2),
    map(() => {
      throw new Error('error');
    })
  );
  const finalNumber = await firstValueFrom(source$)
    .then((value) => {
      console.log(value);
    })
    .catch((error) => {
      console.log(`Error:`, error); // Error: Error {}
    })
    .finally(() => {
      console.log('finally'); // finally
    });
  console.log(`The final number is ${finalNumber}`);
}
execute();



記事はここまでとなります。
最後までお読み頂きありがとうございました。

Discussion