使用变量修改器修改 RxJS observable - Angular
Mutating a RxJS observable with variable mutators - Angular
我做了一个从远程源获取一些用户数据的服务。该服务是一种获取多个用户的方法,一种获取特定用户的方法。
来自这两个方法的可观察对象 returned 通过 map() 获得 .pipe(ed) 以便能够在用户对象被使用之前改变它们。
我想要的是只为多用户流和单用户流定义一次修改器,但我 运行 遇到了我当前方法的范围问题。
请注意,我称用户为“英雄”。这是一个设计方面。
以下是我的 HeroService class:
中的相应方法
export class HeroService {
// [...]
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.addSkillsToHero)),
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.splitName))
);
}
getHero(id): Observable<Hero> {
return this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map((hero: Hero) => this.addSkillsToHero(hero)),
map((hero: Hero) => this.splitName(hero))
);
}
private mutateHeroes(heroes: Hero[], mutator) {
heroes.forEach((hero: Hero) => {
hero = mutator(hero);
});
return heroes;
}
private splitName(hero: Hero) {
let heroNames: string[] = hero.name.split(" ");
hero.firstname = heroNames.splice(0, 1).join(" ");
hero.lastname = heroNames.splice(heroNames.length - 1, 1).join(" ");
hero.middlename = heroNames.join(" ");
return hero;
}
private addSkillsToHero(hero: Hero) {
hero.skills = this.getSkills(Math.ceil(Math.random() * 10));
return hero;
}
private getSkills(count: number): string[] {
let skills: string[] = [];
let i;
for (i = 0; i < count; i++) {
skills.push(this.getRandomSkill());
}
return skills;
}
private getRandomSkill(): string {
return SKILL_TAGS[Math.floor(Math.random() * SKILL_TAGS.length)];
}
// [...]
}
Observable 的 catchError() return:Cannot read property 'getSkills' of undefined
我怀疑增变器没有在 class 范围内被调用,因此无法找到。
我如何在 JS 中做这样的事情?
可以在以下位置检查整个项目:
问题出在这里:
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.addSkillsToHero)),
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.splitName))
);
}
这样传递 this.addSkillsToHero
,不会保留 this
引用。您正在传递对函数的引用,this
是在调用时确定的。您要么需要将该函数绑定到您需要的 this
,要么使用箭头函数为您捕获 this
。您可以在此处找到更多详细信息:JavaScript function binding (this keyword) is lost after assignment or https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
您可以这样尝试:
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.addSkillsToHero.bind(this))),
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.splitName.bind(this)))
);
}
或使用箭头函数:
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, h => this.addSkillsToHero(h))),
map((heroes: Hero[]) => this.mutateHeroes(heroes, h => this.splitName(h)))
);
}
救援箭头函数 (Lamdas)
如前所述,您的问题归结为函数上下文 (this
) 的决定方式。这是在 运行 时间完成的事情。
要扩展此处给出的另一个答案,您可以定义一个已绑定其上下文 (this
) 的函数。诀窍是尽早调用该函数并使用该函数的部分应用版本。一种方法是使用 bind
.
const functionName = (function (args){
/* Some code */
return /*something*/
}).bind(this);
这有点丑陋,所以 JavaScript 有做同样事情的箭头函数。
const functionName = (args) => {
/* Some code */
return /*something*/
}
那更漂亮了。更好的是 一行 单表达式 Lamdas 有一个隐含的 return:
const functionName = (args) => {
return /*something*/;
}
// Is the same as
const functionName = (args) => /*something*/;
普通函数在调用时会被赋予一个上下文。箭头函数总是将它们定义的范围绑定为它们的上下文。如果您不使用 Javascript 的原型继承,通常没有太多理由需要从定义它的范围更改函数的上下文。
我发现如果您愿意稍微学习一下这些知识,您真的可以整理您的代码。
以下是我将完全使用箭头函数编写您的 HeroService class 的方式。如果这是最好的方法,还有待商榷。当然,这种方法允许更多 declarative/functional 风格。在实践中,两者的结合可以让你两全其美。
export class HeroService {
// [...]
public getHeroes = (): Observable<Hero[]> =>
this.http.get<Hero[]>(this.heroesUrl).pipe(
map(this.mutateHeroes(this.addSkillsToHero)),
map(this.mutateHeroes(this.splitName))
);
public getHero = (id): Observable<Hero> =>
this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map(this.addSkillsToHero),
map(this.splitName)
);
private mutateHeroes = transformer =>
(heroes: Hero[]) => heroes.map(transformer);
private splitName = (hero: Hero) => {
let heroNames: string[] = hero.name.split(" ");
return ({
...hero,
firstName: heroNames.splice(0, 1).join(" "),
lastname: heroNames.splice(heroNames.length - 1, 1).join(" "),
middlename: heroNames.join(" ")
});
}
private addSkillsToHero = (hero: Hero) => ({
...hero, skills: this.getSkills(Math.ceil(Math.random() * 10))
});
private getSkills = (count: number): string[] =>
Array.from({length: count}).map(this.getRandomSkill);
private getRandomSkill = (): string =>
SKILL_TAGS[Math.floor(Math.random() * SKILL_TAGS.length)];
// [...]
}
额外:组合函数
一个很酷的 属性 正确实现的映射函数(不管你是在数组还是流上映射)是:
map(x => f(g(x)))
===
map(g).map(f)
这告诉我们,如果我们组合 addSkillsToHero
和 splitName
,那么您可以用一个 map
而不是两个来做到这一点。 RxJS 流实际上是换能器,所以它们(在某种程度上)为你做这件事,但数组映射不是这样你也可以通过组合获得一些边际性能(你只迭代你的数组一次!)。
通常你有一个像 Ramda/Redux/Lodash/etc 这样的库,你可以写像
这样的东西
private addSkillsAndSplitName =
compose(this.addSkillsToHero, this.splitName);
但是用 vanilla JavaScript 组合两个函数也很容易。
private addSkillsAndSplitName =
hero => this.addSkillsToHero(this.splitName(hero))
然后你可以像这样使用这个新功能:
export class HeroService {
// [...]
public getHeroes = (): Observable<Hero[]> =>
this.http.get<Hero[]>(this.heroesUrl).pipe(
map(this.mutateHeroes(this.addSkillsAndSplitName))
);
public getHero = (id): Observable<Hero> =>
this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map(this.addSkillsAndSplitName)
);
而且您没有在可测试性、灵活性或关注点分离方面受到影响,因为功能仍然存在于单独的函数中。
我做了一个从远程源获取一些用户数据的服务。该服务是一种获取多个用户的方法,一种获取特定用户的方法。 来自这两个方法的可观察对象 returned 通过 map() 获得 .pipe(ed) 以便能够在用户对象被使用之前改变它们。
我想要的是只为多用户流和单用户流定义一次修改器,但我 运行 遇到了我当前方法的范围问题。
请注意,我称用户为“英雄”。这是一个设计方面。 以下是我的 HeroService class:
中的相应方法export class HeroService {
// [...]
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.addSkillsToHero)),
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.splitName))
);
}
getHero(id): Observable<Hero> {
return this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map((hero: Hero) => this.addSkillsToHero(hero)),
map((hero: Hero) => this.splitName(hero))
);
}
private mutateHeroes(heroes: Hero[], mutator) {
heroes.forEach((hero: Hero) => {
hero = mutator(hero);
});
return heroes;
}
private splitName(hero: Hero) {
let heroNames: string[] = hero.name.split(" ");
hero.firstname = heroNames.splice(0, 1).join(" ");
hero.lastname = heroNames.splice(heroNames.length - 1, 1).join(" ");
hero.middlename = heroNames.join(" ");
return hero;
}
private addSkillsToHero(hero: Hero) {
hero.skills = this.getSkills(Math.ceil(Math.random() * 10));
return hero;
}
private getSkills(count: number): string[] {
let skills: string[] = [];
let i;
for (i = 0; i < count; i++) {
skills.push(this.getRandomSkill());
}
return skills;
}
private getRandomSkill(): string {
return SKILL_TAGS[Math.floor(Math.random() * SKILL_TAGS.length)];
}
// [...]
}
Observable 的 catchError() return:Cannot read property 'getSkills' of undefined
我怀疑增变器没有在 class 范围内被调用,因此无法找到。
我如何在 JS 中做这样的事情?
可以在以下位置检查整个项目:
问题出在这里:
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.addSkillsToHero)),
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.splitName))
);
}
这样传递 this.addSkillsToHero
,不会保留 this
引用。您正在传递对函数的引用,this
是在调用时确定的。您要么需要将该函数绑定到您需要的 this
,要么使用箭头函数为您捕获 this
。您可以在此处找到更多详细信息:JavaScript function binding (this keyword) is lost after assignment or https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
您可以这样尝试:
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.addSkillsToHero.bind(this))),
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.splitName.bind(this)))
);
}
或使用箭头函数:
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, h => this.addSkillsToHero(h))),
map((heroes: Hero[]) => this.mutateHeroes(heroes, h => this.splitName(h)))
);
}
救援箭头函数 (Lamdas)
如前所述,您的问题归结为函数上下文 (this
) 的决定方式。这是在 运行 时间完成的事情。
要扩展此处给出的另一个答案,您可以定义一个已绑定其上下文 (this
) 的函数。诀窍是尽早调用该函数并使用该函数的部分应用版本。一种方法是使用 bind
.
const functionName = (function (args){
/* Some code */
return /*something*/
}).bind(this);
这有点丑陋,所以 JavaScript 有做同样事情的箭头函数。
const functionName = (args) => {
/* Some code */
return /*something*/
}
那更漂亮了。更好的是 一行 单表达式 Lamdas 有一个隐含的 return:
const functionName = (args) => {
return /*something*/;
}
// Is the same as
const functionName = (args) => /*something*/;
普通函数在调用时会被赋予一个上下文。箭头函数总是将它们定义的范围绑定为它们的上下文。如果您不使用 Javascript 的原型继承,通常没有太多理由需要从定义它的范围更改函数的上下文。
我发现如果您愿意稍微学习一下这些知识,您真的可以整理您的代码。
以下是我将完全使用箭头函数编写您的 HeroService class 的方式。如果这是最好的方法,还有待商榷。当然,这种方法允许更多 declarative/functional 风格。在实践中,两者的结合可以让你两全其美。
export class HeroService {
// [...]
public getHeroes = (): Observable<Hero[]> =>
this.http.get<Hero[]>(this.heroesUrl).pipe(
map(this.mutateHeroes(this.addSkillsToHero)),
map(this.mutateHeroes(this.splitName))
);
public getHero = (id): Observable<Hero> =>
this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map(this.addSkillsToHero),
map(this.splitName)
);
private mutateHeroes = transformer =>
(heroes: Hero[]) => heroes.map(transformer);
private splitName = (hero: Hero) => {
let heroNames: string[] = hero.name.split(" ");
return ({
...hero,
firstName: heroNames.splice(0, 1).join(" "),
lastname: heroNames.splice(heroNames.length - 1, 1).join(" "),
middlename: heroNames.join(" ")
});
}
private addSkillsToHero = (hero: Hero) => ({
...hero, skills: this.getSkills(Math.ceil(Math.random() * 10))
});
private getSkills = (count: number): string[] =>
Array.from({length: count}).map(this.getRandomSkill);
private getRandomSkill = (): string =>
SKILL_TAGS[Math.floor(Math.random() * SKILL_TAGS.length)];
// [...]
}
额外:组合函数
一个很酷的 属性 正确实现的映射函数(不管你是在数组还是流上映射)是:
map(x => f(g(x)))
===
map(g).map(f)
这告诉我们,如果我们组合 addSkillsToHero
和 splitName
,那么您可以用一个 map
而不是两个来做到这一点。 RxJS 流实际上是换能器,所以它们(在某种程度上)为你做这件事,但数组映射不是这样你也可以通过组合获得一些边际性能(你只迭代你的数组一次!)。
通常你有一个像 Ramda/Redux/Lodash/etc 这样的库,你可以写像
这样的东西private addSkillsAndSplitName =
compose(this.addSkillsToHero, this.splitName);
但是用 vanilla JavaScript 组合两个函数也很容易。
private addSkillsAndSplitName =
hero => this.addSkillsToHero(this.splitName(hero))
然后你可以像这样使用这个新功能:
export class HeroService {
// [...]
public getHeroes = (): Observable<Hero[]> =>
this.http.get<Hero[]>(this.heroesUrl).pipe(
map(this.mutateHeroes(this.addSkillsAndSplitName))
);
public getHero = (id): Observable<Hero> =>
this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map(this.addSkillsAndSplitName)
);
而且您没有在可测试性、灵活性或关注点分离方面受到影响,因为功能仍然存在于单独的函数中。