Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does RxJS subscribe allow omitting the arrow function and the following method argument?

Recently I needed to use RxJS. I tried to design an error handling flow, but I discovered some weird syntax passing method arguments:

.subscribe(
    x => {
    },
    console.warn // <- Why does this compile, and warn 'is not 7' in debug console?
);

Link to minimal reproduction:

https://stackblitz.com/edit/rxjs-6-5-error-handle-no-arrow-issue

Steps to reproduce:

  1. Use RxJS 6.5
  2. Create a function return observable
  3. Subscribe to observable
  4. Pass parameter into subscribe
  5. Just use ,console.warn, not like ,error => { console.warn(error); }

Without an arrow function, it still passes errors to console.warn. Why?

Code:

import { throwError, concat, of } from "rxjs";
import { map } from "rxjs/operators";

const result = concat(of(7), of(8));

getData(result).subscribe(
  x => {
    console.log("typeof(x)", typeof(x));
    if (typeof(x) === 'string') {
      console.log("x  Error", x);
      return;
    }
    console.log("no error", x);
  },
  console.warn // <- Why does this compile, and warn 'is not 7' in debug console?
);

// pretend service method
function getData(result) {
  return result.pipe(
    map(data => {
      if (data !== 7) {
        throw "is not 7";
      }
      return data;
    })
  );
}

I tried to google some keywords, js, rxjs, angular, omit arrow function, argument missing,... but I cannot locate what tech is used here.

Could anyone provide links to where this mechanism is explained?

The following two questions are related but do not explain the behavior, just say "equivalent":

The line

map(this.extractTreeData)

is the equivalent of

map(tree => this.extractTreeData(tree))

How to pass extra parameters to RxJS map operator

Why is argument missing in chained Map operator

like image 966
Paul0000 Avatar asked Jul 31 '20 07:07

Paul0000


2 Answers

First you need to understand what you're actually passing to the .subscribe function. Essentially it accepts three optional arguments next, error and complete. Each of it is a callback to be executed when the corresponding notification is emitted by the source observable.

So when you're using an arrow function, you define an in-place callback function.

sourceObservable.subscribe({
  next: (value) => { },
  error: (error) => { },
  complete: () => { }
});

Instead you could define the functions separately and use it as callbacks.

onNext(value) {
}

onError(error) {
}

onComplete() {
}

sourceObservable.subscribe({
  next: this.onNext,
  error: this.onError,
  complete: this.onComplete
});

Now this is what you're seeing. But instead of a user-defined function, you're passing the built-in console.warn() function. And in-turn the values from the notifications will be passed as arguments to the callback functions. So the value from your error is not 7 is sent as argument to console.warn() which then does it's job (i.e. prints to the console).

However there's a catch. If you wish to refer to any of the class member variables using the this keyword in the callback, it'll throw an error saying the variable is undefined. That's because this refers to the scope of the function in the callback and not the class. One way to overcome this is to use an arrow function (we've seen that already). Or use bind() function to bind the meaning of this keyword to the class.

sourceObservable.subscribe({
  next: this.onNext.bind(this),
  error: this.onError.bind(this),
  complete: this.onComplete.bind(this)
});

So if you wish to only have the error callback for example, you could explicitly state it and ignore the others.

sourceObservable.subscribe({ error: console.warn });

Now as to your question "why no parentheses in the function call", it was discussed here and here. The arguments expect a reference to a function and the function names denotes their reference.

like image 130
ruth Avatar answered Nov 14 '22 20:11

ruth


console.log is a function

function can be called with arguments in a bracket

console.log("123") means call function console.log with argument "123"

tree => console.log(tree) is also a function

it can also be called with arguments in a bracket, eg. (tree => console.log(tree))(tree)

so a function with callback as its argument can call its callback with arguments in a bracket

function example(callback) {
callback();
}

so if we pass console.log to it, example(console.log), it basically runs as

function example(callback) {
console.log();
}

if we pass tree => console.log(tree) to it, example(tree => console.log(tree)), it basically runs as

function example(callback) {
(tree => console.log(tree))();
}

if you understood above code. it's easy to understand subscribe now.

function subscribe(nextCb, errorCb, completeCb) {
// ... got next data
nextCb(data);
//... got error
errorCb(error);
// completed observe
completeCb();
} 

so your error callback console.log basically get called as console.log(error);

error=> console.log(error) basically get called as (error=> console.log(error))(error);

which in this case results are same.

like image 39
arslan2012 Avatar answered Nov 14 '22 20:11

arslan2012