【问题标题】:Angular subscribe to data returned from APIAngular 订阅从 API 返回的数据
【发布时间】:2018-09-28 15:27:42
【问题描述】:

我正在尝试清理我的 Ionic 应用程序并将冗余功能移动到提供程序中。下面是我的大多数 API 函数的示例(为了简洁起见,我删除了很多不相关的东西)并且它们运行良好。

getDataFromApi() {
    ....
    Promise.all([
        ...
    ]).then((result) => {
        let headers = new Headers();
        ...
        let body = new FormData();
        ...
        this.http.post(url, body, headers)
            .map(res => res.json())
            .subscribe(data => {
                if (data == '{}') {
                    this.mydata = [];
                } else {
                    this.mydata = data;
                }
            }, error => { });
    });
}

所以我所做的就是将函数移到提供程序中并像这样进行更改

getDataFromApi() {
    ....
    Promise.all([
        ...
    ]).then((result) => {
        let headers = new Headers();
        ...
        let body = new FormData();
        ...
        return this.http.post(url, body, headers));
    });
}

然后在页面的构造函数中我这样称呼它

this.service.getDataFromApi()
    .map(res => res.json())
    .subscribe(data => {
        if (data == '{}') {
            this.mydata = [];
        } else {
            this.mydata = data;
        }
    }, error => { });

显然这是行不通的。我已经倾注了 2 天的 SO 帖子,也无法获得其他人的示例和答案以正常工作。我已经尝试在提供程序中进行映射并在页面中调用它时进行订阅,但无论我尝试什么,都会不断收到错误。我知道这个函数正在返回数据,因为我可以在将它移动到提供程序之前看到它。

我做错了什么?

【问题讨论】:

  • 你必须返回你的 promise.all :) getDataFromApi() { .... return Promise.all([ ... ])
  • 从回调返回不同于从创建它的函数返回。
  • 你能告诉我们“Promise.all”位在做什么吗?一般来说,您希望完全处理可观察对象,而不是来回更改 - 但不知道您在这些承诺中做了什么,很难知道...

标签: angular ionic-framework


【解决方案1】:

我认为最重要的是保持一致;您可以使用 only 承诺或使用 only observables 来完成所有这些操作(而不是混合使用两者)。

请查看 this stackblitz demo,您可以在其中了解如何仅使用 observables 发出类似的 HTTP 请求,以及仅使用 Promise 发出完全相同的请求。


使用可观察对象

您可以将switchMap(或任何其他展平运算符)与fromPromise 一起使用,以从您的提供者处以相同的方法处理所有这些:

// Angular
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// RxJS 
// Please notice that this demo uses the 5.5.2 version
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators/map';
import { tap } from 'rxjs/operators/tap';
import { switchMap } from 'rxjs/operators/switchMap';

// ...

// Get a list of 5 users using observables
public getDataUsingObservables(): Observable<any> {
  const promises = Promise.all([
    this.somePromise('getDataUsingObservables', 1),
    this.somePromise('getDataUsingObservables', 2)
  ]);

  return fromPromise(promises)
    .pipe(
      switchMap(results => {
        console.log(`[getDataUsingObservables]: Both promises are finished`);
        const url = `https://randomuser.me/api/?results=5`;

        return this.http.get<any>(url);
      }),
      tap(res => {
        console.log(`[getDataUsingObservables]: The http request is finished`);
      })
  );
}

// Return a promise with the number sent as parameter 
private somePromise(callerMethod: string, aNumber: number): Promise<number> {
  console.log(`[${callerMethod}]: About to create a promise with the number ${aNumber}`);
  return Promise.resolve(aNumber);
}

然后你会像这样使用它:

this.dataService.getDataUsingObservables().subscribe(
  response => {
    this.responseUsingObservables = response;
  }, 
  error => {
    // Handle the error...
    alert(error);
  });

使用承诺

如果你想使用promise,你可以使用toPromise()操作符将observable转化为promise:

// Angular
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// RxJS 
// Please notice that this demo uses the 5.5.2 version
import { tap } from 'rxjs/operators/tap';

// ...

// Get a list of 5 users using promises
public getDataUsingPromises(): Promise<any> {
  const promises = Promise.all([
      this.somePromise('getDataUsingPromises', 1),
      this.somePromise('getDataUsingPromises', 2)
  ]);

  return promises
    .then(results => {
      console.log(`[getDataUsingPromises]: Both promises are finished`);
      const url = `https://randomuser.me/api/?results=5`;

      return this.http.get<any>(url)
        .pipe(
          tap(res => {
            console.log(`[getDataUsingPromises]: The http request is finished`);
          })
        )
        .toPromise();
    });
}

// Return a promise with the number sent as parameter 
private somePromise(callerMethod: string, aNumber: number): Promise<number> {
  console.log(`[${callerMethod}]: About to create a promise with the number ${aNumber}`);
  return Promise.resolve(aNumber);
}

然后你会像这样使用它:

this.dataService.getDataUsingPromises()
  .then(response => {
    this.responseUsingPromises = response;
  })
  .catch(error => {
    // Handle the error...
    alert(error);
  });

【讨论】:

  • 我在使用它时无法得到任何回报。即使我做this.service.someNewMethodAsPromise().then(data =&gt; { console.log('here') } 我什么也得不到;控制台中没有打印。
  • 能否请您每隔一两行代码添加一些console.log(...),看看执行的是什么部分?
  • 运行 this.service.someNewMethodAsPromise().then(data =&gt; { console.log('here'); }) 没有输出,但 this.service.someNewMethodAsPromise().then(console.log('here')) 输出 here
  • @timgavin 嗯,这很奇怪。请您看看我创建的 this stackblitz demo,我使用 only promisesonly observables 获取数据。在该示例中,我使用了答案中的代码,并添加了一些 tap 管道,只是为了在控制台中打印一条消息(如果需要,您可以删除它们)。
  • 这对我不起作用,可能是因为我使用的是旧版本的 Angular,现在无法升级。不过谢谢!
【解决方案2】:

免责声明:可能/应该有一种方法可以在 observables 中实现完全相同的概念,但我对它们不太熟悉,所以也许有人可以详细说明。

你的函数应该返回一个承诺,这意味着你会这样做

getDataFromApi() {
    ....
    return Promise.all([
        ...
    ]);
}

然后在调用函数的 then 部分实现它的逻辑。现在这并不好玩,因为您将复制您试图消除的代码。

为此,您的函数仍应返回一个承诺,该承诺在您想要的 return 语句之后解析,如下所示:

getDataFromApi() {
  ....
  new Promise((resolve, reject) => {
    Promise.all([
      ...
    ]).then(result => {
      const headers = new Headers();
      ...
      const body = new FormData();
      ...
      // This is equavilent to your desired return
      resolve(this.http.post(url, body, headers));
    });
  });
}

然后在调用者then 块中实现 POST 订阅,如下所示:

this.service.getDataFromApi()
  .then(observ => {
    observ.subscribe(result => {
    // Use your response here
    });
  });

你甚至可以使用异步函数,这在代码中会更容易,而不是将函数包装在一个新的 Promise 中,你可以只在 Promise.all 上使用 await,然后是随后的 return将被翻译成为你解决的承诺。

编辑: 更好的是,如果您每次只订阅 JSON 数据,并希望它返回,则可以推广该概念以使调用者中的代码更简洁:

this.service.getDataFromApi().then(finalData => {
  // This is the final returned data ...
}, (err) => {
//allows error to be handled
});

这可以通过在订阅块的providers方法中解析promise来实现,如下所示:

getDataFromApi() {
  return new Promise((resolve, reject) => {
    Promise.all([...]).then(result => {
      const headers = new Headers();
      const body = new FormData();
      this.http
        .post(url, body, headers)
        .map(res => res.json())
        .subscribe(data => {
          if (data == '{}') {
            reject([]);
          } else {
            resolve(data);
          }
        });
    });  
  })
}

【讨论】:

  • @StephenRomero 不幸的是,这没有用;同样的错误。但是,我确实注意到 Ionic 吐出了另一个错误:resolve is not defined,所以我删除了resolve(data) 并用return data 替换它,但仍然收到uncaught (in promise) 错误。
  • 我必须说,混合 Promise 和 observables 是一种不好的做法,因为您可以使用 only promises 或 only observables(使用 observables实际上是 Angular 中推荐的方式,因为 http 客户端和其他一些核心功能已经基于 observables 实现)。
  • @sebaferreras 你说得对!这就是我在文章开头留下免责声明的原因,因为我对 observables 了解不多。刚刚赞成你的答案:)
  • @StephenRomero 当它们用于 API 请求时,同样的事情也可以应用于 observables(我们都假设 API 会返回一些东西,即使它是一个错误)。 Promise 和 observables 都可以用于 API 请求,但 observables 比 Promise 更强大(例如,您可以使用 observables 取消 ongoing 请求,但您不能使用 Promise 做到这一点)。我更喜欢使用 observables 以与 angular 处理异步代码的方式保持一致,但当然,你也可以只使用 promises,结果应该是相似的。
  • @sebaferreras 谢谢你,我会在 Observables 上做更多的研究。
猜你喜欢
  • 2016-07-20
  • 1970-01-01
  • 1970-01-01
  • 2017-02-23
  • 2019-05-27
  • 2020-08-06
  • 2020-06-23
  • 2021-07-16
  • 2021-12-05
相关资源
最近更新 更多