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
Optionwould break many people.firstwill 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
Observablewhilst there any chance the TC39 proposal might be resurrected. IMO,toPromiseshould be deprecated for eventual removal from theObservableprototype 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
forkJoinat least not in this version. I assume there are probably people who’re using it in conjunction with atakeUntilor 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
forkJoinis. 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