What’s New in RxJS 7
The most well-known and widely used library of reactive programming in JavaScript is RxJS. So much so that the number of downloads per week today is around 25 million (2.3x React, 9.7x Angular, 11x Vue). We have been using RxJS 6 for 3 years. RxJS 7, whose first alpha version was released in September 2019, was released six days ago. So what are the changes awaiting us in RxJS 7? What are the differences between RxJS 7 and 6? Should we switch to RxJS 7?
TypeScript, size, memory, and speed in RxJS 7
Better types
One of the major changes to RxJS 7 is perhaps the least noticeable. When we look at the changelog, we see that there is a lot of work on the types. Especially, efforts on the following subjects stand out:
- Functions that take
n
parameters, such asof
, can now correctly infer types even when over 8-9 parameters are passed to them.
- We can separate the groups created by
groupBy
into their types according to thekey
.
- With
filter(Boolean)
, we can filter out the types that do not pass the condition.
- The
next
method now takes theSubject
type into account.
Smaller bundle size
Ben Lesh says, by going over the entire library one by one, they reduced the total package size by up to 39%. This is a great effort and a noteworthy result. So, I tried this in a small Angular project. The results are as follows:
Using only RxJS 7 instead of RxJS 6 had an effect of 10kB on total packet size. Not bad for a small application. In addition, we see the change has more effect on the size of the lazy chunk in which I used many operators. 👏💯
Less memory consumption
In RxJS, we call an Observable
that emits another Observable
a "higher-order observable".
Here, the Observable
that we created with of
is called the "outer observable", and the one we created with fromFetch
is called the "inner observable". In RxJS 6, operators also capture the values emitted by outer observables. In RxJS 7, this commit eliminated the need for capturing outer values and reduced memory consumption.
A faster RxJS
Honestly, I haven’t tried this part myself. Ben Lesh’s descriptions and what I have heard here and there are in this direction. See tweet below:
Of course, the tweet doesn’t claim “20%” is a fixed value, but the refactoring and improvements on the codebase must have worked.
You may have seen it on the tweet, but don’t get too excited: support for
for await
has since been removed.
Notable Features and Changes in RxJS 7
toPromise → firstValueFrom, lastValueFrom
Let’s start with a change that most people using RxJS 6 have heard of: toPromise
is now deprecated. But don't be afraid right away; it hasn't been removed from the library yet. So, toPromise
can still be used in RxJS 7. However, I suggest you clear all toPromise
s in your project as soon as possible because it will be removed soon. Also, the type it returns is set to be undefined
now, so it can break projects that use TypeScript in strict mode.
So what can you turn it into? First, let’s remember how the toPromise
method works:
Why 5
and not 1
? This is because toPromise
returns a Promise
that resolves with the last value emitted when the source Observable
completes. In RxJS 7, instead of toPromise
, we can use two different functions: firstValueFrom
and lastValueFrom
.
In case you are wondering if there is a difference between lastValueFrom
and toPromise
, yes there is. Namely, toPromise
would be parsed with the value undefined
if the source Observable
terminated without emitting a value.
This is a little unfortunate because:
- The
Promise
returned bytoPromise
can indeed emitundefined
. There is no way to tell the difference from an error state. - The case where no value is emitted should be treated as an error.
The new function lastValueFrom
throws a custom error with the shape EmptyErrorImpl
when Observable
s terminate without emitting a value. Note the use of catch
in the example below.
So how do we detect this error? We will use the EmptyError
class for this.
Don’t get confused by EmptyError
and EmptyErrorImpl
here. The following part from the source code explains why we see two different names for the same error:
You might think why they didn’t do this:
The reason is new EmptyError() instanceof EmptyError
expression is expected to return true
, but when TypeScript is compiled into ES5, it returns false
. (See TypeScript #12123). RxJS team used Object.create
to overcome this, and the constructor
of the error object has a different name than the error type. This approach applies to all custom errors thrown by RxJS.
They could have the same name, but this commit stopped overriding of the
no-shadowed-variable
TSLint rule and required names to be different.
combineLatest observable dictionary
In RxJS 6, we could pass Observable
dictionaries to forkJoin
.
This is now also possible with combineLatest
in RxJS 7.
combineLatestWith
The combineLatest
operator was deprecated in RxJS 6.
In RxJS 7, it was replaced by the combineLatestWith
operator.
mergeWith
The merge
operator was deprecated in RxJS 6.
In RxJS 7, it was replaced by the mergeWith
operator.
zipWith
The zip
operator was deprecated in RxJS 6.
In RxJS 7, it was replaced by the zipWith
operator.
raceWith
The race
operator was deprecated in RxJS 6.
In RxJS 7, it was replaced by the raceWith
operator.
concatWith
The concat
operator was deprecated in RxJS 6.
In RxJS 7, it was replaced by the concatWith
operator.
A more powerful timeout
Before RxJS 7, we could do the following to set a separate timeout for the initial value that a stream would emit:
Frankly, the control sequence above was a bit too complex for something as simple as this. With RxJS 7, we can now pass a configuration object to the timeout
operator:
Also, with this configuration object, the timeoutWith
operator has been deprecated and replaced by the with
parameter. In RxJS 6, we did the following:
In RxJS 7, we do this:
resetOnSuccess option for the retry operator
In RxJS 6, retry
would only take the number of retries as its parameter.
This number was not reset after successful attempts. In RxJS 7, we can now pass the retry
operator an option named resetOnSuccess
.
share configuration
In RxJS 7, the share
operator has started to receive a fairly extensive configuration object. In fact, it seems like there is no need for shareReplay
anymore. Let's remember how shareReplay
worked in RxJS 6:
Let’s see how we can do this with the new share
configuration.
Properties that start with “reset” in the configuration object enable Observable
to be reset and become "cold" again when the relevant state occurs. For example, resetOnRefCountZero
resets the resource if the number of connected Observer
s becomes zero again due to unsubscribe
.
connect & connectable
RxJS 7 has a new connect
operator to multicast a source Observable
. It works like this:
There is also a new function named connectable
, which returns ConnectableObservableLike
objects. We could do the above example like this:
With a modification on the last release candidate, connectable
started receiving a configuration object with connector
and resetOnDisconnect
properties. Take a look at how we disconnect and connect again in the example below.
Since connectable
, connect
and share
are sufficient for multicasting, the multicast
, publish
, publishBehavior
, and publishLast
operators have been deprecated.
animationFrames
In RxJS 7, there is a new observable that wraps requestAnimationFrame
and cancelAnimationFrame
functions: animationFrames
. As the name suggests, it is used to create animations.
The simple animation created by the above code is as follows:
config.onUnhandledError
In RxJS 7, the config
object contains an asynchronous catcher for unhandled errors: onUnhandledError
.
RxJS custom errors get call stack back
At the beginning of the article, we mentioned that RxJS uses Object.create
for custom errors. Actually, Object.setPrototypeOf
was used earlier, but while developing v6.3.0, this was abandoned to support IE10. In October 2018, an issue was created to inform that custom errors lost their call stack. Finally, with 7.0.0-beta.5, the call stack has been added back to errors.
Conclusion
There are many more changes in RxJS 7, but neither my strength to explain all nor your energy to read would suffice. I tried to gather what I believe to be important in this article. Maybe this version isn’t as dramatic as the transition from 4 to 5 or 5 to 6, but I should note that RxJS is now an established library.
There are many deprecations in RxJS 7, but, to make the upgrade easy, they haven’t deleted anything. This is great news. Nonetheless, TypeScript users, in particular, are likely to have difficulty at times. I think it makes a lot of sense to switch to RxJS 7 for ongoing projects. I will do that for my projects at the first opportunity, and I recommend it to you too.
I hope it was useful. See you in another article.