rxjs: toPromise should reject if no value is emitted.
Current Behavior
toPromise
will resolve with undefined
if the source observable simply completes without emitting a value.
EMPTY.toPromise().then(x => console.log(x)); // logs "undefined"
The problem
Promises are a guarantee to a future value. With toPromise()
we should be saying “this is the last value we’ve gotten prior to completion”. But what we’re actually saying is “this might be the last value we’ve gotten prior to completion”. It’s semantically unsound, IMO.
If the developer wants a guaranteed value from a source, and that value might be undefined
, there’s currently no way for that developer to discern whether or not the undefined
that toPromise
resolved with was the undefined
from the source, or just happenstance from completion without a value.
const promiseTheresAValue1 = of(1, 2, 3, undefined).toPromise();
const promiseTheresAValue2 = EMPTY.toPromise(); // Oops, there wasn't actually a value
Proposal
Have toPromise
reject with an EmptyError
if no value is emitted prior to completion of the source.
EMPTY.toPromise()
.then(() => console.log('not hit'))
.catch(err => console.log(err instanceof EmptyError)); // logs "true"
Optionally we could introduce a last()
or lastValue()
method to Observable
and deprecate toPromise
to reduce confusion while migrating to this change, while also being able ot introduce this better semantic sooner.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 9
- Comments: 18 (12 by maintainers)
Commits related to this issue
- feat(lastValue): Adds lastValue method to Observable This is meant to be a more well-behaved, a better-named, replacement for `toPromise`. Resolves #5075 — committed to benlesh/rxjs by benlesh 5 years ago
- feat(lastValue): Adds lastValue method to Observable This is meant to be a more well-behaved, a better-named, replacement for `toPromise`. Resolves #5075 — committed to benlesh/rxjs by benlesh 5 years ago
My understanding is that we want to limit breaking changes to types and incorrect behaviours only. Introducing something like
Option
would break many people.first
will throw, as willsingle
. There was some discussion aboutforkJoin
, too, but I’d need to look at the source to see what came of that.I’d be wary of introducing anything else to
Observable
whilst there any chance the TC39 proposal might be resurrected. IMO,toPromise
should be deprecated for eventual removal from theObservable
prototype and a static function should be exported instead.@cartant I’m not sure if this conflicts with any previous statements I’ve made, but I’m not sure we can do that with
forkJoin
at least not in this version. I assume there are probably people who’re using it in conjunction with atakeUntil
or something to short-circuit things in some cases. Maybe I’m wrong.I mean, it’s totally debatable. It’s a matter of principal about what we wanted to say
forkJoin
is. If we wanted to say it’s a guarantee of the last value from each observable, then yeah, I guess it should error. If we wanted to say it’s primarily a signal that all observables have completed, and we’re happening to give you their last values, then it shouldn’t error.One reason for my wanting it removed from the protoype, is that I cannot recall using it outside of some tests that I wrote years ago.
It seems a little weird to have switched to static, pipeable, tree-shakeable operators, but still have (what ought to be, IMO) a rarely-used method on the prototype.
@cartant, that’s an interesting proposal. As far as the observable proposal goes, it might still be a moonshot, so I think RxJS, as a library, should work towards making sure our developer ergonomics make sense.
Honestly, they’re all a little unpalatable. lol