【问题标题】:Is there any way to chain n amount of http requests together using RxJS?有没有办法使用 RxJS 将 n 个 http 请求链接在一起?
【发布时间】:2017-05-24 14:28:10
【问题描述】:

在下面的代码中,我有一个 User 对象,它可以有可变数量的帐户 ID 与之关联。当用户更新时,应为用户对象中包含的每个帐户发出额外的请求:

class User {

  id: number;

  accountIds: Array<number>;
}

// Side effects class
@Effect()
update$ = this.actions$
  .ofType('USER_UPDATE')
  .switchMap(action => {

    let user = action.payload;

    for (let accountId of user.accountIds) {
      let account = { id: accountId, userIds: [user.id] };

      // Is there any way to chain these api calls with switch maps
      // and then finally switch map to the user request below?
      return this.apiService.post(`/accounts/${account.id}`, account);
    }

    return this.apiService.post(`/users/${userid}`, user);
  }

有没有办法使用 RxJS 使用 switchMap(或类似的东西)将这些调用链接在一起,这样 observable 才被认为是完整的直到所有后续请求都完成而无需编写一些一种自定义的递归逻辑?

【问题讨论】:

  • 也许您想使用Observable.forkJoin(如果顺序无关紧要)...将所有请求存储在Array 中并像这样使用:Observable.forkJoin(&lt;myRequestsArray&gt;)...
  • 是的,我看到了 forkJoin,似乎并行发送所有请求,这不是我所追求的,但看起来是迄今为止最好的方法。谢谢。
  • 这些链接很好,谢谢@martin

标签: angular rxjs angular-http


【解决方案1】:

如果你只想用 Observable 来处理这个问题(这在效果中似乎是个好主意),你可以这样做:

const { Observable } = Rx;

// DATA
const user = {
  id: 'currentUserId',
  accountIds: [
    'accountId0',
    'accountId1',
    'accountId2',
    'accountId3'
  ]
}

// MOCK BACKEND CALLS
const apiService = {
  post: (url, data) => {
    return Observable.of(`some response for ${url}`).delay(1000);
  }
};

// HANDLE THE MULTIPLE HTTP CALLS
Observable
  .from(user.accountIds)
  .map(accountId => ({ id: accountId, userIds: [user.id] }))
  .map(account => apiService.post(`/accounts/${account.id}`, account))
  .concatAll()
  .do(console.log)
  .last()
  .do(_ => console.log('Finished to chain the HTTP calls for accounts'))
  .switchMap(_ => apiService.post(`/users/${user.id}`, user))
  .do(console.log)
  .subscribe();

输出为:

我制作了一个 Plunkr,因此您可以看到它的实际效果:
https://plnkr.co/edit/xFWXyAJM6qm0XwMf4tmv?p=preview


这里有趣的部分是我们如何处理调用。
让我给你一些更多的细节:

from 允许我们将每个 accountIds 一个接一个地发送到 observable :

  .from(user.accountIds)

然后我们可以为每个accountId 构建我们想要发送到后端的对象:

  .map(accountId => ({ id: accountId, userIds: [user.id] }))

一旦完成,我们会创建一个 cold Observable,一旦我们订阅它就会发起 HTTP 调用:

  .map(account => apiService.post(`/accounts/${account.id}`, account))

感谢concatAll,我们得到了您所期望的行为:一个接一个地进行每个 HTTP 调用:

  .concatAll()

显示 HTTP 调用的响应:

  .do(console.log)

现在,我们想在对accounts 的每个请求完成后向users 发出最终请求。 last 将帮助我们等待最后一个(船长明显在这里): 。最后的() 只需记录对accounts 的每个请求都已完成

  .do(_ => console.log('Finished to chain the HTTP calls for accounts'))

users进行最后的HTTP调用 .switchMap(_ => apiService.post(/users/${user.id}, 用户)) 输出响应

  .do(console.log)

当然我们需要订阅,因为这是一个冷的 Observable,否则什么都不会发生。 * .subscribe();

*:在你的效果中,你可能想简单地返回 Observable,因为 ngrx/effects 会为你订阅它;)

【讨论】:

  • Maxime 非常感谢您的详细回答。我接受了 ZahiC,因为它更简洁,但你的回答很有价值,我感谢你并支持它
  • 我希望更多的人会在他们的答案中提供这种详细程度。不要太多或太少。很好的答案
  • 欣赏评论!谢谢:)
【解决方案2】:

试试这个:

update$ = this.actions$
    .ofType('USER_UPDATE')
    .pluck('payload')
    .switchMap(user =>      
        Observable.from(user.accountIds)
            .concatMap(accountId => 
                this.apiService.post(`/accounts/${accountId}`, {id: accountId, userIds: [user.id]})
            )
            .concat(Observable.defer(() => this.apiService.post(`/users/${user.id}`, user)))
    );

以下是硬编码数据的示例: https://jsfiddle.net/96x907ae/1/

【讨论】:

  • 你是个相当聪明的 Observablesmith @ZahiC
【解决方案3】:

我想你正在寻找Observable.concat

From the docs:

concat 将多个 Observable 连接在一起,通过一次订阅一个并将它们的结果合并到输出 Observable 中。您可以传递 Observable 数组,也可以将它们直接作为参数。传递一个空数组将导致 Observable 立即完成。

concat 将订阅第一个输入 Observable 并发出其所有 值,而不以任何方式改变或影响它们。当那个 Observable 完成,它将订阅然后下一个 Observable 通过 并再次发出它的值。这将重复,直到操作员 用完 Observables。当最后一个输入 Observable 完成时,concat 也将完成。在任何给定时刻,只有一个 Observable 通过 to 运算符发出值。如果您想从传递的值中发出值 Observables 同时检查,而不是检查合并,尤其是 可选并发参数。事实上, concat 是一个 相当于并发参数设置为 1 的合并运算符。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多