为什么 RxJS 订阅允许省略箭头函数和以下方法参数?
Why does RxJS subscribe allow omitting the arrow function and the following method argument?
最近需要用到RxJS。我试图设计一个错误处理流程,但我发现了一些奇怪的语法传递方法参数:
.subscribe(
x => {
},
console.warn // <- Why does this compile, and warn 'is not 7' in debug console?
);
Link 最小复制:
https://stackblitz.com/edit/rxjs-6-5-error-handle-no-arrow-issue
重现步骤:
- 使用 RxJS 6.5
- 创建一个函数 return observable
- 订阅 observable
- 将参数传递给订阅
- 就用
,console.warn
,不像,error => { 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;
})
);
}
我尝试google一些关键字,js,rxjs,angular,省略箭头函数,缺少参数,...但我找不到这里使用的技术。
谁能提供解释此机制的链接?
下面两个问题相关但不解释行为,只说“等价”:
The line
map(this.extractTreeData)
is the equivalent of
map(tree => this.extractTreeData(tree))
Why is argument missing in chained Map operator
首先,您需要了解实际传递给 .subscribe
函数的内容。本质上它接受三个可选参数 next
、error
和 complete
。每一个都是一个回调,当相应的通知被源可观察对象发出时执行。
所以当你使用箭头函数时,你定义了一个就地回调函数。
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
关键字引用任何 class 成员变量,它会抛出一个错误,指出该变量未定义。那是因为 this
指的是回调函数的作用域,而不是 class。克服这个问题的一种方法是使用箭头函数(我们已经看到了)。或者使用bind()
函数将this
关键字的含义绑定到class.
sourceObservable.subscribe({
next: this.onNext.bind(this),
error: this.onError.bind(this),
complete: this.onComplete.bind(this)
});
因此,如果您希望仅包含错误回调,您可以明确声明并忽略其他回调。
sourceObservable.subscribe({ error: console.warn });
关于你的问题“为什么函数调用中没有括号”,已经讨论过here and here。参数需要对函数的引用,函数名称表示它们的引用。
您的订阅需要 2 个参数。第一个参数是一个将使用“下一个”值调用的函数,第二个参数也是一个函数,如果发生错误将调用该函数。因此,由于“console.warn”是一个函数,您可以将它用作第二个参数。
(error) => console.warn(error)
等同于 console.warn(error)
但是要小心,因为 console.warn
不依赖于上下文“this”,你不会给出任何问题。但是如果你想使用一个使用上下文“this”的函数,你将需要使用箭头函数。
有关 JS 作用域的更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
这是由于 Observable 能够发出的三种可能类型的值,
- 下一个
- 错误
- 完成
这些逻辑在订阅函数的参数中进行了翻译,因此第一个函数回调将触发通过 next 发出的值,第二个回调将触发错误发出的值,第三个函数将触发完整值。
在你的例子中,console.warn 作为函数传递给第二个函数,每次发出错误时都会调用该函数。
第二个问题可以参考箭头函数文档,https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
console.log
是函数
可以用括号中的参数调用函数
console.log("123")
表示使用参数 "123"
调用函数 console.log
tree => console.log(tree)
也是函数
它也可以用括号中的参数调用,例如。
(tree => console.log(tree))(tree)
因此以 回调作为其参数的函数 可以使用括号中的参数调用其回调
function example(callback) {
callback();
}
所以如果我们将 console.log
传递给它,example(console.log)
,它基本上运行为
function example(callback) {
console.log();
}
如果我们将 tree => console.log(tree)
传递给它,example(tree => 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=> console.log(error)
基本上被称为 (error=> console.log(error))(error)
;
在这种情况下结果相同。
在 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(() => {}, () => {});
[为什么]显示警告'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);}
);
的原则
最近需要用到RxJS。我试图设计一个错误处理流程,但我发现了一些奇怪的语法传递方法参数:
.subscribe(
x => {
},
console.warn // <- Why does this compile, and warn 'is not 7' in debug console?
);
Link 最小复制:
https://stackblitz.com/edit/rxjs-6-5-error-handle-no-arrow-issue
重现步骤:
- 使用 RxJS 6.5
- 创建一个函数 return observable
- 订阅 observable
- 将参数传递给订阅
- 就用
,console.warn
,不像,error => { 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;
})
);
}
我尝试google一些关键字,js,rxjs,angular,省略箭头函数,缺少参数,...但我找不到这里使用的技术。
谁能提供解释此机制的链接?
下面两个问题相关但不解释行为,只说“等价”:
The line
map(this.extractTreeData)
is the equivalent of
map(tree => this.extractTreeData(tree))
Why is argument missing in chained Map operator
首先,您需要了解实际传递给 .subscribe
函数的内容。本质上它接受三个可选参数 next
、error
和 complete
。每一个都是一个回调,当相应的通知被源可观察对象发出时执行。
所以当你使用箭头函数时,你定义了一个就地回调函数。
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
关键字引用任何 class 成员变量,它会抛出一个错误,指出该变量未定义。那是因为 this
指的是回调函数的作用域,而不是 class。克服这个问题的一种方法是使用箭头函数(我们已经看到了)。或者使用bind()
函数将this
关键字的含义绑定到class.
sourceObservable.subscribe({
next: this.onNext.bind(this),
error: this.onError.bind(this),
complete: this.onComplete.bind(this)
});
因此,如果您希望仅包含错误回调,您可以明确声明并忽略其他回调。
sourceObservable.subscribe({ error: console.warn });
关于你的问题“为什么函数调用中没有括号”,已经讨论过here and here。参数需要对函数的引用,函数名称表示它们的引用。
您的订阅需要 2 个参数。第一个参数是一个将使用“下一个”值调用的函数,第二个参数也是一个函数,如果发生错误将调用该函数。因此,由于“console.warn”是一个函数,您可以将它用作第二个参数。
(error) => console.warn(error)
等同于 console.warn(error)
但是要小心,因为 console.warn
不依赖于上下文“this”,你不会给出任何问题。但是如果你想使用一个使用上下文“this”的函数,你将需要使用箭头函数。
有关 JS 作用域的更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
这是由于 Observable 能够发出的三种可能类型的值,
- 下一个
- 错误
- 完成
这些逻辑在订阅函数的参数中进行了翻译,因此第一个函数回调将触发通过 next 发出的值,第二个回调将触发错误发出的值,第三个函数将触发完整值。
在你的例子中,console.warn 作为函数传递给第二个函数,每次发出错误时都会调用该函数。
第二个问题可以参考箭头函数文档,https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
console.log
是函数
可以用括号中的参数调用函数
console.log("123")
表示使用参数 "123"
console.log
tree => console.log(tree)
也是函数
它也可以用括号中的参数调用,例如。
(tree => console.log(tree))(tree)
因此以 回调作为其参数的函数 可以使用括号中的参数调用其回调
function example(callback) {
callback();
}
所以如果我们将 console.log
传递给它,example(console.log)
,它基本上运行为
function example(callback) {
console.log();
}
如果我们将 tree => console.log(tree)
传递给它,example(tree => 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=> console.log(error)
基本上被称为 (error=> console.log(error))(error)
;
在这种情况下结果相同。
在 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(() => {}, () => {});
[为什么]显示警告'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);}
);
的原则