Curiosity leads to discoveries.
RxJS is quite a popular library in Angular world so for me it is always interesting to find out something new about its internals. Today I want to dig into utils folder of its GitHub repo. This article is written not for some practical usage but rather for reverse engineering enthusiasts and my curiosity.
Here is a link to RxJS 6.5 /src/internal/util/ dir. This folder is full of files. Let’s review them one by one. Some of them can bring something interesting 🔍.
#1 ArgumentOutOfRangeError.ts
The main code is next:
function **ArgumentOutOfRangeErrorImpl** (this: any) {
Error.call(this);
this.message = 'argument out of range';
this.name = 'ArgumentOutOfRangeError';
return this;
}
_ArgumentOutOfRangeErrorImpl_.prototype = Object._create_(Error.prototype);
_export const_ ArgumentOutOfRangeError : ArgumentOutOfRangeErrorCtor = _ArgumentOutOfRangeErrorImpl as any_;
What is interesting here?
a) We inherit from built-in Error class here. So we can throw calls instance with the message we need.
b) superclass constructor is called in an explicit way: Error.call(this);
Where it is used in RxJS source code?
Let's take a look at takeLast(count) operator — it should emit last count values after the source observable is complete. Of cause it cannot accept negative count — so:
When using takeLast(i), it delivers an ArgumentOutOrRangeError to the Observer's error callback if i < 0
at line 63:
if (this.total < 0) { throw new ArgumentOutOfRangeError; }
Why should I know this?
Reading source code can help you to understand how the library works in edge cases. And sometimes it is the only way to understand why it works how it works.
#2 EmptyError.ts
It looks very similar to ArgumentOutOfRangeError
function EmptyErrorImpl(this: any) {
Error.call(this);
this.message = 'no elements in sequence';
this.name = 'EmptyError';
return this;
}
EmptyErrorImpl.prototype = Object.create(Error.prototype);
export const EmptyError: EmptyErrorCtor = EmptyErrorImpl as any;
But the application scope is different.
Where it is used in code?
Let's review first operator — it should emit only the first value (or the first value that meets some condition) emitted by the source Observable.
But what is sequence is empty? We can find an answer at line 69 of rxjs/src/internal/operators/first.ts
Delivers an EmptyError to the Observer's `error` callback if the Observable completes before any `next` notification was sent.
And this happens in lines 86–90:
return (source: Observable<T>) => source.pipe(
predicate ? filter((v, i) => predicate(v, i, source)) : identity,
take(1),
hasDefaultValue ? defaultIfEmpty<T | D>(defaultValue) : throwIfEmpty(() => new **EmptyError** ()),
);
*Interesting remark: you can use take(1) instead of first() with almost the same result:
source$.pipe(take(1))
vs
source$.pipe(first())
The only difference that take operator will not emit EmptyError if source$ completes before producing value.
Why should I know this?
Since first operator uses EmptyError — a final code bundle should contain EmptyError.ts file too. So when you use first operator — a final bundle size will be a bit bigger. You can read more about it here.
I am preparing my future video-course with advanced techniques of mastering Angular/RxJS. Want to get a notification when it is done? Leave your email here (and get free video-course): http://eepurl.com/gHF0av
Like this article? Follow me on Twitter!