はじめに
toPromiseはRxJS7で非推奨となりRxJS8で使用できなくなることが予定されています。
今後はtoPromiseの代わりにlastValueFromを利用するべきという記事が多くありますが、本当に同じことができるのかを検証してみました。
結論
多くのケースでは問題ないが全く同義ではない
下記公式ドキュメントでもこう記載されています。
RxJS Conversion to Promises
The lastValueFrom is almost exactly the same as toPromise()
almost(ほぼ)ということで異なる挙動をするケースがあるのでこの記事ではその説明をしていきます。
toPromise終了に向けて追加された2つのFunction
lastValueFromとtoPromiseの違いの前にまずtoPromiseの終了に向けて追加された2つのFunctionを説明します。
fistValueFrom
firstValueFromはtoPromise同様にObservableなオブジェクトをPromiseに変換する機能を持ったFunctionです。
firstValueFromでは引数とした受け取ったObservableが最初にnextした値をresolveして返してくれます。
firstValueFrom(from([1, 2, 3])).then((value) => {
console.log(value);
});
// expected output: 1
Angularなどを利用している場合、stateをobservableな値で持っている場合も多くそれをasync awaitで同期処理のように書きたい場合などで非常に便利な関数です。
lastValueFrom
lastValueFromもObservableなオブジェクトをPromiseに変換する機能を持ったFunctionですが、firstValueFromとは異なり最後にnextした値をresolveして返してくれます。
lastValueFrom(from([1, 2, 3])).then((value) => {
console.log(value);
});
// expected output: 3
toPromiseとそれぞれの関数の違い
それでは本題のtoPromiseとの違いについて説明します。
firstValueFromとの違い
これは明らかですね。
toPromiseでresolveされるのは最後にnextされた値ですが、firstValueFromでresolveされるのは最初にnextされた値です。
firstValueFrom(from([1, 2, 3])).then((value) => {
console.log(value);
});
// expected output: 1
from([1, 2, 3]).toPromise().then((value) => {
console.log(value);
});
// expected output: 3
ただし、BehaviorSubjectなどで最新値をお取得したい場合などの処理は少し簡略化して書くことができるようになってます。
from([1, 2, 3]).pipe(first()).toPromise().then((value) => {
console.log(value);
});
// expected output: 1
firstValueFrom(from([1, 2, 3])).then((value) => {
console.log(value);
});
// expected output: 1
上記の2つは同義です。
lastValueFromとの違い
lastValueFromもtoPromiseも最後にnextした値をresolveするという点では同じなので一見同じ処理に見えますが異なる動きをするケースがあります。
それは
値がnextされずにcompleteされた場合
です。
lastValueFrom(EMPTY).then((value) => {
console.log(value);
}).catch((error) => {
console.log('error');
});
// expected output: 'error'
EMPTY.toPromise().then((value) => {
console.log(value);
}).catch((error) => {
console.log('error');
});
// expected output: undefined
toPromiseでは仮にそのオブジェクトが何もnextせずにcompleteしてもundefinedでresolveしてくれますが、lastValueFromでは値がnextされなかった場合はerrorとして返してしまいます。
したがって、toPromiseを副作用なしで代替しようとすると下記のようになります。
lastValueFrom(
EMPTY.pipe(
last(),
catchError((error) => {
if (error instanceof EmptyError) {
return of(undefined);
} else {
throw error;
}
})
)
)
まとめ
toPromiseはRxJS8で削除予定となりますが、移行の際には意味合いをきちんと理解して移行するのが無難かと思います。
最後に紹介したlastをpipeするパターンで書き換えればおそらく互換性を維持して書き換えることが出来ますが、せっかくfirstValueFromなどの便利なFunctionも追加されているのでこれを機に書き換えていくことを強くお勧めします。