【问题标题】:Return function Angular 2返回函数 Angular 2
【发布时间】:2017-10-17 02:07:30
【问题描述】:

我对异步函数有疑问。我有一项服务可以为我提供来自 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 匹配到函数返回的值。我不知道该怎么做。

【问题讨论】:

  • 从函数中返回 promise 或 `observable` 并在 ngOnInit() 中执行 thensubscribe
  • 您无需拍摄快照、订阅或将数据存储在某处。数据已经存在于 observable 中。只需将其作为您需要的对象订阅,或者更好的是,使用异步管道直接在模板中订阅。
  • @BhavikPatel 它已经是一个可观察的。
  • 添加了 [firebase] 和 [angularfire2] 标签
  • @torazaburo - 重点是返回 observable 而不是变量/对象

标签: angular typescript firebase angularfire2


【解决方案1】:

当然,您的代码将不起作用,因为您试图返回尚不存在的东西。最直接的方法(但无论如何都不是最好的)是将展开的 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 返回可以直接订阅的 observables。因此:

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

但是,这种方法有一个严重的缺陷。调用访问器的人将获得 latest 值,但是当他们持有该值时,如果新值到达,他们将永远不会知道它,并且将使用旧值。或者,如果有人在 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>

尝试访问 undefined 上的 name 属性时会出错。最明显的处理方法是使用? 运算符,如

<div> The name is {{data?.name}}</div>

但是,您可能不想在模板中的任何地方都这样做,请记住 AOT 不支持 ?。不愉快的替代方法是将其包装在 &lt;div *ngIf="data"&gt; 中。

因此,首选的方法是将可观察对象作为可观察对象保留到最后一刻,并仅在必要时将其解包(订阅),您可以按以下方式进行操作(使用命名变量的约定以最终形式保存可观察对象$ 为清楚起见):

// 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 的值,而不是在服务或组件中解包它,然后对解包的值执行操作,请使用 map 操作 observable 本身:

public firstName$;

ngOnInit() {
  this.firstName$ = this.service.getData().map(data => data.name.split(' ')[0]);
}

然后在您的模板中使用映射的可观察对象

The first name is
  <div *ngIf="firstName$ | async as firstName"; else loading">{{firstName}}</div>
  <ng-template #loading>not loaded yet</ng-template>

总而言之,使用 AngularFire 可观察对象以及所有可观察对象的首选模式是尽可能长时间地将它们作为可观察对象,通过将它们映射(或过滤)到新的可观察对象来操作它们,避免打开它们并存储如果可能的话,它们的值在本地,最后在实际需要值的点订阅,这在模板中很常见,订阅可以由async 管道处理。

使用async 管道还有另一个主要优势。如果您在组件逻辑中显式订阅,则必须记住该订阅,然后在ngOnDestroy 中取消订阅以避免内存泄漏。相比之下,Angular 会自动为您清理使用 async 管道隐式完成的所有订阅。

【讨论】:

  • 非常好的答案!我记得我在订阅构造函数中的可观察对象时遇到了一些麻烦。 OnInit 在编写测试时也是如此。那时真的很难模拟或覆盖订阅。
【解决方案2】:

另一种选择是使用awaitasync 来简化代码:

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 等待任何promise 的完成,这也意味着您从该方法返回的任何值都将被包装到一个promise 中,因此调用者必须使用await或将其视为承诺并致电.then()

请记住,尽管它使代码像同步代码一样读取,但一切仍然异步发生,因此在异步部分完成之前不会设置值。然而,由于 Angular 已经知道如何处理不应该成为问题的异步 ngOnInit(),它会等待结果。

【讨论】:

  • 这当然是另一种选择,但恕我直言,我没有看到使用 Promises 的好处。当然 rxjs 提供了这个选项,但是我们使用 Observables 是有原因的,不再是 Promise。我认为宣传它不是一件好事。
  • @lexith,observables 和 promises 都有自己的位置。在这种情况下,使用 promise 可以让代码布局更接近同步形式,因此我认为这里是更好的选择。
  • @lexith 对于单值流,也就是大多数 XHR,老实说,promise 更有意义。甚至 Rx 的创建者 Erik Meijer 也表示,Tasks/Promises 是单个异步传递值的正确抽象。 async/await 的代码清晰和简洁在 CRUD 应用程序中非常有用。当然,有时您需要自动完成或轮询,或跨 Web 服务调用,在这种情况下 RxJS 是完美的,但要为工作选择正确的工具。
  • 这没有利用 AngularFire 接口的特性,它的全部目的是为您提供可按原样使用的可观察对象,而无需快照、等待或取消引用。
  • @torazaburo 这绝对是真的。我没有注意到实际使用的服务this.af.database,我只是看到了一个磨机异步问题的运行。感谢您指出这一点。
【解决方案3】:

我建议您阅读RXJS Observables 的工作原理:RXJS Doc

基本上你想做的就是从你的component订阅你的Observable。您的服务应该只返回(或者可能在返回之前操纵传入的数据)它。

这可能看起来像这样:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-19
    • 2018-07-14
    • 1970-01-01
    相关资源
    最近更新 更多