如何使用RxJS并行处理多个HTTP调用

时间:2018-09-18 16:37:37

标签: angular typescript rxjs

背景

我正在构建一个Angular应用程序,其中列出了在给定地址注册的公司。

提供一些背景信息...

假设我有3家公司:A公司,B公司和C公司。

公司B与公司A的注册地址相同。公司C不是。

当我导航至该应用并在公司A上进行过滤时,我希望仅在列表中看到公司B。

问题

我的问题不是我无法正常工作,而是太慢了!我需要以某种方式利用多线程/并发。

为了弄清楚在给定地址注册了哪些公司,我必须进行几次HTTP调用。

在解释我进行HTTP调用的顺序之前。让我向您展示API的外观:

function send_counter(counter) {
    $.ajax({
      url   : "index.php", 
      type  : "POST",
      cache : false,
      data  : { counter : counter, submit: 1 }
    });
  }
});

对,这是顺序:

  1. 获取公司A的地址并存储ID
  2. 获取公司A的链接
  3. 遍历每个链接 3a。获取链接地址 3b。检查地址ID是否与公司A的地址ID相匹配。如果是这样,请存储链接。

当前实施

<?php
if (isset($_POST['submit'])){
    $counter=$_POST['counter'];
    echo "$counter";
}
?>

必须有一个更优雅/性能更高的解决方案!

任何帮助将不胜感激:)

谢谢

2 个答案:

答案 0 :(得分:1)

您是正确的,因为这些API调用中的每个调用都必须先等待另一个完成。

您本身无权使用多线程,但是您可以在编写时使用Promise.all使其并发。具体来说,您可以同时检索地址和链接,然后同时检索所有其他地址。我已经缩短了一些方法调用名称,以便于编写:

const [companyAAddress, companyALinks] = await Promise.all(
  getAddress(id).toPromise(),
  getLinks(id).toPromise(),
);

const companiesToShow = await Promise.all(links.map(link => getAddress(link.id).pipe(
  map(linkAddress => [link, linkAddress]),
  filter(([, { id }]) => id === companyAAddress.id),
).toPromise())

您可以严格使用Observables来做到这一点,而不是使用promises。

forkJoin(getAddress(id), getLinks(id)).pipe(
  mergeMap(([companyAAddress, companyALinks]) => companyALinks.map(link => getAddress(link.id).pipe(
    map(linkAddress => [link, linkAddress]),
    filter(([, { id }]) => id === companyAAddress.id),
  ))
)

但是,我要说的是,服务器应该实现从单个请求中获取链接公司的功能。

答案 1 :(得分:0)

您应该在这里考虑其他布局。

选项1

将该业务逻辑移至服务器端。从问题的描述中,我看不到客户端以任何方式简化了检索B地址的过程。服务器端拥有检索所有链接地址所需的所有信息。

您只需要一个电话到后端。而且您可以在这里避免使用await,如果服务器无法快速响应,这会使用户的触觉也变慢。

服务器端的伪代码:

// this is a new endpoint on the backend which holds your current client logic
on(/api/companies/<company>/address_links) {

  // retrieve the address with it's id like your first endpoint and store it temporary in address/id
  address = getCompanyAddress(company); 

  // query all all addresses with that id/address and store it in links[]
  other_companies[] = getCompaniesWithAddress(address);

  // Build your response JSON
  return_me = {
    id: address.id,
    addressLine1: address.line1,
    links: other_companies 
  }

  // send the response
  response.send(return_me);
}

选项2

重新设计后端,并使用websocket加快查询速度。您还可以避免同时HTTP请求的限制(每个服务仅一个请求)。

如果连接尚未建立,但是Websocket对于单个请求的速度可能会慢一些,但开销却比几个HTTP请求小得多。可以在这里找到非常好的文章(来自一个名为FeathersJS的非常好的框架的作者):https://blog.feathersjs.com/http-vs-websockets-a-performance-comparison-da2533f13a77

您更改组件代码以监听观察者

this.subscription = this.myService.addressObserver$.subscribe((data: any) => {
  [Assign and do something]
}

并更改服务以使用套接字(不完整且不可运行,仅是示例):

import * as feathers from '@feathersjs/client';
import * as io from 'socket.io-client';
[...]

@Injectable()
export class MyService {
  private readonly feathersService: any;
  public myObserver$: Subject<any>;

  constructor() {
    const socket = io(<url>, {
      transports: ['websocket'],
      forceNew: true,
    });

    const feathersApp = feathers().configure(feathers.socketio(socket));
    this.feathersService = feathersApp.service('api/address');

    // on (create, delete, update) methods work like realtime when something changed on the server
    this.feathersService.on('updated', (address) => this.onUpdated(address));
  }

  // single find, can be adapted to query by something
  public find(): void {

    // the actual query to the server 
    this.feathersService.find(address).then((addresses: any) => {
      this.myObserver$.next(addresses.data);
    });
  }

  ...

选项3

两者都做