angular - 将嵌套的 forkjoins 返回为 observable
问题描述
我试图在我的解析器中返回一堆嵌套的 forkjoins 和普通订阅。为此,我尝试使用地图,但我认为我还没有完全掌握地图/switchMaps/mergeMaps 的概念。我知道代码还没有返回 UserResult,这是因为我还不知道如何将 questionAnswers 添加到 UserResult,但这对我当前的问题应该没有太大的区别。
我的目标是重写它,以便它返回一个可观察的。
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<UserResult> {
const questionAnswers = Array<QuestionAnswer>();
this.rs.getResult(this.auth.token, route.params['id']).subscribe(res => {
forkJoin(
this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
this.accs.getAccount(res.userId)
).subscribe(results => {
forkJoin(
this.accs.getUserDetails(results[1].access_token),
this.as.getAnswers(this.auth.token)
).subscribe(results2 => {
results[0].forEach(cat => {
this.cs
.getQuestionsCategory(this.auth.token, cat.id)
.subscribe(questions => {
results2[1]
.filter(ans => ans.userId === results[1].uid)
.forEach(a => {
const question = questions.find(q => q.id === a.questionId);
if (!isNullOrUndefined(question)) {
const category = results[0].find(
c => c.id === a.categoryId
);
const qa = new QuestionAnswer(question, a);
qa.category = category.name;
questionAnswers.push(qa);
}
});
});
});
});
});
});
}
我试着像这样重写它,但它根本不起作用。我收到了一些未定义的错误,但它们都指向管道的开头,没有什么特别的。
const questionAnswers = Array<QuestionAnswer>();
let res;
let res2;
return this.rs.getResult(this.auth.token, route.params['id']).pipe(
map((res: Result) =>
forkJoin(
this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
this.accs.getAccount(res.userId)
)
),
tap(results => (res = results)),
map(results =>
forkJoin(
this.accs.getUserDetails(results[1].access_token),
this.as.getAnswers(this.auth.token)
)
),
tap(results2 => (res2 = results2)),
map(
res[0]
.forEach(cat => {
this.cs.getQuestionsCategory(this.auth.token, cat.id);
})
.map(questions =>
res2[1]
.filter(ans => ans.userId === res[1].uid)
.forEach(a => {
const question = questions.find(q => q.id === a.questionId);
if (!isNullOrUndefined(question)) {
const category = res[0].find(c => c.id === a.categoryId);
const qa = new QuestionAnswer(question, a);
qa.category = category.name;
questionAnswers.push(qa);
}
})
)
)
);
编辑
我刚刚注意到,在点击 results2 后 res[0] 会导致
无法读取未定义的属性“0”
我认为这与我对水龙头的不当使用有关,因为它在我试图更改的订阅中运行良好。
编辑2
我将代码拆分为 Kurt 推荐的更小的函数,但是我不太确定如何将它与我用于类别的 forEach 一起使用。我也不知道应该在哪里创建我将作为可观察对象返回的最终对象
getResultByRouteParamId(route: ActivatedRouteSnapshot): Observable<Result> {
return this.rs.getResult(this.auth.token, route.params['id']);
}
forkJoinQuizCategoriesAndAccount(
result: Result
): Observable<[Category[], Account]> {
return forkJoin(
this.quizs.getCategoriesQuiz(this.auth.token, result.quizId),
this.accs.getAccount(result.userId)
);
}
forkJoinUserDetailsAndAnswers(results: [Category[], Account]) {
return forkJoin(
this.accs.getUserDetails(results[1].access_token),
this.as.getAnswers(this.auth.token)
);
}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<UserResult> {
const questionAnswers = Array<QuestionAnswer>();
let result: Result;
let res: [Category[], Account];
let res2: [User, Answer[]];
return this.getResultByRouteParamId(route).pipe(
tap(resu => result = resu),
switchMap((result: Result) => this.forkJoinQuizCategoriesAndAccount(result)),
tap(results => (res = results)),
switchMap(results => this.forkJoinUserDetailsAndAnswers(results)),
tap(results2 => (res2 = results2)),
switchMap(
// Stuck here!
res[0]
.forEach(cat => {
this.cs.getQuestionsCategory(this.auth.token, cat.id);
})
.map(questions =>
res2[1]
.filter(ans => ans.userId === res[1].uid)
.forEach(a => {
const question = questions.find(
(q: Question) => q.id === a.questionId
);
if (!isNullOrUndefined(question)) {
const category = res[0].find(
(c: Category) => c.id === a.categoryId
);
const qa = new QuestionAnswer(question, a);
qa.category = category.name;
questionAnswers.push(qa);
}
}
// let ur = new UserResult(res2[1], result)
// ur.questionAnswers = questionAnswers;
// return ur;
)
)
)
);
解决方案
所以......那是你到达那里的一大块 RxJS。
首先要做的事情——你不订阅 RxJS 操作符——你将 observables 链接在一起。
一些定义
switchMap
并concatMap
用于将一个 observable 的结果传递给另一个。
map
用于将值从一种结构转换为另一种结构(类似于同名数组函数的概念)。
forkJoin
组合多个可观察对象,并在它们全部完成时返回一个结果。
你的代码
在您开始尝试整理代码之前,我建议您考虑将每个步骤拆分为自己的功能。这将有望帮助您查看数据流并考虑您的依赖项在哪里。
我曾尝试将您的原始示例转换为 RxJS,但在考虑每个步骤实际试图实现的目标时有点迷茫。
我确实确定的是你最终会得到一个有点像这样的模式(我订阅这个演示的目的——你会返回 observable):
result: string;
ngOnInit() {
this.initialValue().pipe(
switchMap(result => this.forkJoinOne(result)),
switchMap(result => this.forkJoinTwo(result)),
switchMap(result => this.forkJoinThree(result)),
map(result => this.mapFour(result))
).subscribe(result => {
this.result = result;
});
}
private initialValue(): Observable<string> {
return of('zero');
}
private forkJoinOne(result: string): Observable<string[]> {
return forkJoin([
of(`${result} one`),
of('four')
]);
}
private forkJoinTwo(results: string[]): Observable<string[]> {
return forkJoin([
of(`${results[0]} two`),
of(`${results[1]} five`)
]);
}
private forkJoinThree(results: string[]): Observable<string[]> {
return forkJoin([
of(`${results[0]} three`),
of(`${results[1]} six`)
]);
}
private mapFour(results: string[]): string {
return results.join(' ');
}
每个可观察的步骤都被移到了它自己的函数中——这可以帮助你思考需要输入什么数据以及输出什么数据——你实际上是在每个步骤之间创建了一个契约。
这switchMap
只是采取一个可观察的并设置另一个。最后map
是获取前一个 observable 的输出并将其转换为不同的值。
我在这里使用了字符串,希望可以轻松地跟踪数据流。我建议首先尝试理解我的简单示例,然后使用这些原则重新构建您的函数。
演示:https ://stackblitz.com/edit/angular-eedbqg
我的版本在以下方面与您的大致一致:
初始值
this.rs.getResult(this.auth.token, route.params['id'])
forkJoinOne
所有的叉连接都应该传入一个数组或一个对象。我更喜欢传入对象的相对较新的方式,它表示发出值的结构。(forkJoin({ a: myObs })
返回{ a: value }
)。
forkJoin(
this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
this.accs.getAccount(res.userId)
)
forkJoinTwo
forkJoin(
this.accs.getUserDetails(results[1].access_token),
this.as.getAnswers(this.auth.token)
)
叉加入三
您需要将此循环转换为可观察的数组,并将其传递给forkJoin
.
results[0].forEach(cat => {
this.cs.getQuestionsCategory(this.auth.token, cat.id)
地图四
你需要整理你的地图。而不是forEach
这里,更喜欢filter
和map
(数组函数)。
map(questions =>
res2[1]
.filter(ans => ans.userId === res[1].uid)
.forEach(a => {
const question = questions.find(q => q.id === a.questionId);
if (!isNullOrUndefined(question)) {
const category = res[0].find(c => c.id === a.categoryId);
const qa = new QuestionAnswer(question, a);
qa.category = category.name;
questionAnswers.push(qa);
}
})
推荐阅读
- jquery - 如何在jquery中强调现有的内部html?
- python - 如何减小字符分隔字符串的大小?
- algorithm - 不懂伪代码:(Q[i] = j + r - i) 或 (Q[i] = j - r + i)
- typescript - tsconfig 路径和桶文件/相同目录导入的嵌套依赖项解析问题
- kubernetes - 设置不允许使用 UDP 的 kubernetes
- azure-application-insights - AppInsights 采样始终或更频繁地包括慢速调用
- python - 在 xlsxwriter 中格式化 pandas 数据框重复工作表名称
- java - 如何在 ListView 和 AlertDialog 中设置自定义字体?
- c# - 如何获取另一个驱动器的当前工作目录?
- javascript - 数组声明