【问题标题】:Why does RxJS subscribe allow omitting the arrow function and the following method argument?为什么 RxJS subscribe 允许省略箭头函数和下面的方法参数?
【发布时间】:2020-11-21 01:06:41
【问题描述】:

最近我需要使用 RxJS。我试图设计一个错误处理流程,但我发现了一些奇怪的语法传递方法参数:

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

链接到最小复制:

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

重现步骤:

  1. 使用 RxJS 6.5
  2. 创建一个函数返回 observable
  3. 订阅 observable
  4. 将参数传递给订阅
  5. 只用,console.warn,不像,error =&gt; { console.warn(error); }

如果没有箭头函数,它仍然会将错误传递给 console.warn。为什么?

代码:

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;
    })
  );
}

我尝试用谷歌搜索一些关键字,js、rxjs、角度、省略箭头函数、缺少参数……但我找不到这里使用的技术。

谁能提供解释此机制的链接?

以下两个问题相关但不解释行为,只说“等价”:

线

地图(this.extractTreeData)

相当于

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

How to pass extra parameters to RxJS map operator

Why is argument missing in chained Map operator

【问题讨论】:

  • 函数是javascript中的“第一类对象”。它们是被声明为“内联”还是通过变量引用并不重要
  • console.warn 是一个函数,它期望一个函数,所以很高兴
  • 感谢大家在我提问时留言,让我弄清楚,我来自Java 8,尤其是“函数是一流的对象”是我所需要的

标签: javascript angular rxjs operator-keyword


【解决方案1】:

首先,您需要了解您实际传递给.subscribe 函数的内容。本质上,它接受三个可选参数nexterrorcomplete。每一个都是在源 observable 发出相应通知时执行的回调。

所以当你使用箭头函数时,你定义了一个就地回调函数。

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

相反,您可以单独定义函数并将其用作回调。

onNext(value) {
}

onError(error) {
}

onComplete() {
}

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

现在这就是您所看到的。但是,您传递的是内置的 console.warn() 函数,而不是用户定义的函数。反过来,通知中的值将作为参数传递给回调函数。因此,您的错误 is not 7 中的值作为参数发送到 console.warn(),然后它会完成它的工作(即打印到控制台)。

但是有一个问题。如果您希望在回调中使用 this 关键字来引用任何类成员变量,它将抛出一个错误,指出该变量未定义。那是因为this 指的是回调中函数的范围,而不是类。克服这个问题的一种方法是使用箭头函数(我们已经看到了)。或者使用bind()函数将this关键字的含义绑定到类中。

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

因此,例如,如果您只希望有错误回调,您可以显式声明它并忽略其他回调。

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

现在关于您的问题“为什么在函数调用中没有括号”,已经讨论过herehere。参数需要对函数的引用,而函数名称表示它们的引用。

【讨论】:

  • 感谢还提到了“this”问题,如果遇到意外行为,它真的很有帮助
【解决方案2】:

console.log 是一个函数

函数可以用括号中的参数调用

console.log("123") 表示调用函数console.log 带有参数"123"

tree =&gt; console.log(tree)也是一个函数

它也可以用括号中的参数调用,例如。 (tree =&gt; console.log(tree))(tree)

所以一个以 callback 作为参数的函数 可以用括号中的参数调用它的回调

function example(callback) {
callback();
}

所以如果我们将console.log 传递给它,example(console.log),它基本上运行为

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

如果我们将tree =&gt; console.log(tree) 传递给它,example(tree =&gt; console.log(tree)),它基本上运行为

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

如果你理解了上面的代码。现在订阅很容易理解。

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

所以你的错误回调console.log 基本上被称为console.log(error)

error=&gt; console.log(error) 基本上被称为(error=&gt; console.log(error))(error);

在这种情况下结果是相同的。

【讨论】:

  • OP 将其传递为console.warn(无括号),因此您实际上并未涵盖他们的场景
【解决方案3】:

在 JS 中 functions are first class objects。当您有代码console.warn 没有括号时,您有对该对象的引用但您没有调用该对象,这将需要大括号console.warn()。例如你可以这样做:

let x = console.warn;
console.log('not happened yet');
x('test');

因此,您的代码很简单地将 console.warn 函数传递给 Subscribe 失败的参数,方式与传递任何其他函数的方式完全相同,例如

Subscribe(() => {}, () => {});

[why] show Warn 'is not 7'

另一部分是您抛出错误throw "is not 7";。因此,订阅错误调用的签名是:

subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription;

所以error 的参数是any 类型。因此 throw 将 Error 传递给错误函数处理程序。这设置为console.warn,其签名为:

console.warn(obj1 [, obj2, ..., objN]);

console.warn 本质上是将它传递的任何参数转换为字符串,JS 不是强类型的,这本质上是type coercion,并记录它。 throw "is not 7"; 的字符串是 is not 7。所以它记录了is not 7

总而言之,我想说这有点神秘,可能难以理解。这在技术上没有任何问题,但我会说执行以下操作会更有意义:

.subscribe(
    x => {
    },
    x => {console.warn(x);} 
);

基于"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."的原则

【讨论】:

    【解决方案4】:

    这是由于 Observable 能够发出的三种可能类型的值,

    1. 下一个
    2. 错误
    3. 完成

    这些逻辑是在订阅函数的参数中翻译的,所以第一个函数回调会触发 next 发出的值,第二个回调会触发错误发出的值,第三个函数会触发完整的值。

    在您的情况下,console.warn 作为每次发出错误时都会调用的函数传递给第二个函数。

    第二个问题可以参考箭头函数文档https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

    【讨论】:

    • @Liam,是的,我认为你是对的。我已经进行了相应的更改
    【解决方案5】:

    您的订阅需要 2 个参数。第一个参数是一个函数,将使用“下一个”值调用,第二个参数再次是一个函数,如果发生错误,将调用该函数。因此,由于“console.warn”是一个函数,您可以将其用作第二个参数。

    (error) =&gt; console.warn(error)console.warn(error) 相同

    但是要小心,如果console.warn 不依赖上下文“this”,那么你不会给出任何问题。但是如果你想使用一个使用上下文“this”的函数,你就需要使用箭头函数。

    有关 JS 作用域的更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

    【讨论】:

    • "(error) =&gt; console.warn(error)console.warn(error) 相同":不,不是。 (error) =&gt; console.warn(error)console.warn 大致相同(因此,OP 的问题)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2020-09-06
    • 2016-09-01
    相关资源
    最近更新 更多