我遇到异步函数问题。我有一个服务,它从firebase数据库中提供了一些数据。其中一个函数返回一个值:
historialDeConsumi() {
this.item = this.af.database.object('/users/' + this.uid + '/admin', {
preserveSnapshot: true
});
this.item.subscribe(snapshot => {
this.hijosHistorialConsumi = snapshot.child('HisotialCons').val();
});
return this.hijosHistorialConsumi;
}
我从一个组件中调用此函数:
ngOnInit() {
this.consumi = this.servicioData.historialDeConsumi();
}
我想将this.consumi
与函数返回的值匹配。我不知道这样做的方法。
答案 0 :(得分:4)
当然,您的代码不起作用,因为您正在尝试返回尚不存在的内容。最简单的方法(但不是最好的方法)是将未包装的observable存储在服务变量中,然后提供检索它的方法。在下面,我使用的是简化/样本数据:
// Keep data locally.
private data;
// Listen to observable in constructor, and store locally.
constructor() {
this.item = this.af.database.object(path, {
preserveSnapshot: true
});
this.item.subscribe(snapshot => {
this.data = snapshot.val();
});
}
// Retrieve the data.
getData() { return this.data; }
但请注意,使用快照的整个想法是不必要的。 AngularFire返回可以直接订阅的observable。因此:
constructor() {
this.item = this.af.database.object(path);
this.item.subscribe(data => this.data = data);
}
或更简单地说,只是
constructor() {
this.af.database.object(path).subscribe(data => this.data = data);
}
然而,这种方法存在严重缺陷。无论谁调用访问者都会获得最新值,但是当他们持有该值时,如果新值到达,他们将永远不会知道它,并将使用旧值。或者,如果有人在第一次触发observable之前调用了访问器,这是完全可能的,那么他们最终会获得undefined
,无法再检索后续值。
因此,服务应该只返回observable,组件将使用它。
// SERVICE
getData() { return this.af.database.object(path); }
// COMPONENT
public data;
ngOnInit() {
this.service.getData().subscribe(data => this.data = data);
}
<div>The data is {{data}}</div>
换句话说,我们在ngOnInit
中“展开”observable,并在本地存储其未包装的值。这将是你想要的工作。但是,data
的初始值将是未定义的,直到observable发出其第一个值,因此如果您尝试访问数据上的属性,则假设它是一个对象:
<div>The name is {{data.name}}</div>
尝试访问name
上的undefined
属性时会出错。最明显的处理方法是使用?
运算符,如
<div> The name is {{data?.name}}</div>
但是,您可能不希望在模板中的任何位置都这样做,并记住AOT不支持?
。令人不快的替代方案是将其包裹在<div *ngIf="data">
处。
因此,首选的方法是将observable作为一个observable保留到最后一刻并仅在必要时展开它(订阅它),你可以这样做(使用带有final的命名变量的命名变量的约定) $
为了清楚起见):
// COMPONENT
public data$: Observable<any>;
ngOnInit() {
this.data$ = this.service.getData();
}
<div>The data is {{data$ | async}}</div>
使用隐式订阅的async
管道。如果要在data
上检索属性,则
<div>The name is {{(data$ | async).name}}</div>
一切都会按预期工作。如果你想处理observable尚未发射的情况,那么
The name is
<div *ngIf="data$ | async as data"; else loading">{{data.name}}</div>
<ng-template #loading>not loaded yet</ng-template>
如果你想以某种方式预处理或操纵observable的值,而不是在服务或组件中展开它,然后对展开的值执行操作,使用{{1}操纵observable本身}:
map
然后将模板中映射的observable用作
public firstName$;
ngOnInit() {
this.firstName$ = this.service.getData().map(data => data.name.split(' ')[0]);
}
总之,使用AngularFire可观察对象以及所有可观察对象的首选模式是尽可能长时间地将它们保留为可观察对象,通过将它们映射(或过滤)到新的可观察对象来操纵它们,避免展开它们并存储如果可能的话,在本地使用它们的值,最后在实际需要值的位置订阅,这通常在模板中,其中订阅可以由The first name is
<div *ngIf="firstName$ | async as firstName"; else loading">{{firstName}}</div>
<ng-template #loading>not loaded yet</ng-template>
管道处理。
使用async
管道还有另一个主要优势。如果您在组件逻辑中明确订阅,则必须记住该订阅,然后在async
中取消订阅以避免内存泄漏。相比之下,Angular会自动清除使用ngOnDestroy
管道隐式执行的所有订阅。
答案 1 :(得分:1)
我建议您阅读RXJS Observables
的工作原理:RXJS Doc
您基本上想要做的就是从Observable
订阅component
。您的服务应该返回(或者可能在返回之前操纵传入的数据)。
这看起来像这样:
myServiceFun(): Observable<any> {
return doMyRequest().map((response: any) => {
// manipulate it maybe here
return response;
});
}
在您的组件中,您订阅了此Observable
:
ngOnInit() {
this.backendService.myServiceFunc().subscribe((response: any) => {
this.myComponentProp = response;
});
}
答案 2 :(得分:1)
另一种选择是使用await
和async
来简化代码:
import 'rxjs/add/operator/toPromise';
async historialDeConsumi(){
let item = this.af.database.object('/users/' + this.uid + '/admin', { preserveSnapshot: true });
let snapshot = await item.toPromise();
return snapshot.child('HisotialCons').val();
}
async ngOnInit() {
this.consumi = await this.servicioData.historialDeConsumi();
}
这样,代码看起来非常接近于数据库查询只是普通函数调用的情况。使用toPromise()
将Observable
转换为Promise
。将方法转换为async
方法,并允许您使用await
等待任何承诺的完成,这也意味着您从方法返回的任何值都将包含在承诺中,因此调用者必须使用await
或将其视为承诺并在其上调用.then()
。
请记住,虽然它使代码读取像同步代码一样,但所有内容仍然是异步发生的,因此在异步部分完成之前不会设置该值。但是,由于angular已经知道如何处理不应该成为问题的异步ngOnInit()
,它将等待结果。