繁体   English   中英

对HttpClient结果进行长轮询并将其流式传输到CSV文件中

[英]Long Polling on HttpClient result and streaming into the CSV file

问题1:如何实现相同的行为? 但是它不是Observable.interval ,而是由回调调用。

例如: 我有5000ms的间隔,但我的服务器是非常缓慢的,并且它不回来后的结果5000ms 但是在5000ms之后调用下一个调用。 我不希望它那样。 我希望在结果从服务器返回后它会调用下一个调用。

问题2: 如何在不创建多个文件的情况下立即将结果流式传输到csv文件。 对于当前的实现,我使用在IE11中工作的FileSaver 我想继续使用它。 有没有办法将数据流式传输到文件而不是将其收集到数组中,因为我有大型数据集。 像1米行一样......示例:

const progress = Observable.interval(1000)
  .switchMap(() => this.messageService.getResults(query))
  .map(messageResult => messageResult)
  .subscribe((data: MessagesResult) => {
    inProcess = true;
    if (!data.isMoreResults && data.auditMessageList.length === 0) {
      this.fileSaver.save(`result.csv`, csvData);
      inProcess = false;
      this.logger.info('Download file finished...');
      progress.unsubscribe();
    }
    const start = this.filterModel.offset * this.filterModel.limit;
    const rows = [...csvData];
    rows.splice(start, 0, ...data.auditMessageList);
    csvData = rows;
    if (inProcess) {
      this.logger.info('Exporting in progress...');
    }
    query.offset++;
  }, error => this.logger.error(error));

}

正如您已经发现使用Observable.interval将不会“等待”流的其余部分。

我通常使用repeatWhen delay

const progress = Observable.defer(() => this.messageService.getResults(query))
  .repeatWhen(notifications => notifications.delay(1000)) 
  ...

以下是工作示例: https//jsfiddle.net/a0rz6nLv/19/

我不明白其他人的代码很好。

不要使用progress.unsubscribe(); subscribe方法中。 相反,请考虑使用takeWhiletakeUntil - 两者都将为您完成observable。

.takeWhile(data => data.isMoreResults  data.auditMessageList.length > 0)

缓冲结果也可以通过使用reducetoArray来完成

.reduce((accumulator, data) => data.auditMessageList.concat(accumulator), [])

副作用最好由do操作员处理

.do({
  next: () => {
    inProgress = true;
    this.logger.info('Exporting in progress...');
  },
  complete: () => {
    inProgress = false;
    this.logger.info('Download file finished...');
  }
})

关于第二个问题 - 我不知道 - 您应该能够从服务器流式传输csv。 如果您无法修改服务器,也许其他人将知道如何在客户端上执行此操作...

问题1

这是一个实现函数的示例,该函数在获得响应时调用自身。

后端:

  1. 模拟在5s和10s内响应的慢后端
  2. 在每个响应中,服务器给出当前request_numberstate
  3. 对于3个第一个响应, stateactive ,之后, state closed

码:

/* Mocked backend. I'm slow, like really slow */
class SlowBackend {
  MAX_ITERATIONS = 3; // suppose you're reading a table and you have pagination, with 3 pages
  currentIteration = 0;

  constructor() {}

  getStuff() {
    console.log(`**Request N. ${this.currentIteration}**\n[Back] : received a request from the front`);
    const responseDelay = Math.random() * 5000 + 5000; // response between 5s and 10s
    let state = "open";
    if(++this.currentIteration > this.MAX_ITERATIONS)
      state = "closed";

    return Observable
      .timer(responseDelay)
      .map( () => {
      console.log(`[Back] : Responding after ${responseDelay} ms`)
        return {
          request_number : this.currentIteration,
          state : state
        };

      })
  }
}

面前:

这基本上就是你的组件。

class Frontend {

  isPollingActivated = true;
  responses = [];


  constructor(private backendService) {
    this.backendService = new SlowBackend(); // connection to backend
    this.requestOnRegularBasis();
  }

  requestOnRegularBasis() {
    if (!this.isPollingActivated)
      return;

    this.backendService.getStuff()
      .subscribe(response => {
        console.log(`[Front] : received response from server. State : ${response.state}`);

        // Choose one of the following blocks, comment the other according to what you need

        // Block 1 : Sync processing example
        console.log(`[Front] : doing some sync processing`);
        this.doSomeSyncProcessing(response);
        this.requestOnRegularBasis();

        // Block 2 : Async processing example
        // console.log(`[Front] : doing some async processing`);
        // this.doSomeAsyncProcessing(response)
        //    .subscribe(this.requestOnRegularBasis);

      })
  }

  private doSomeSyncProcessing(response){
    if(response.state == 'closed'){
      this.isPollingActivated = false; // stop polling
      this.saveDataToCsv();
    }
    else
      this.responses.push(Object.values(response).join(';')) // csv line separated by ';'
  }

  private saveDataToCsv(){
    const headers = ['current_request;state']
    this.responses = headers.concat(this.responses)
    console.log('saving to csv : ', this.responses.join('\n'));

    // Uncomment this to use FileSaver API
    /*
    const blob = new Blob(headers.concat(this.responses), {type: "text/csv;charset=utf-8"});
    saveAs(blob, "my_responses.csv");*
    */
  }

  private doSomeAsyncProcessing(response){
    return Observable.timer(1000).map(() => this.doSomeSyncProcessing(response));
  }

}

输出:

**Request N. 0**
[Back] : received a request from the front
[Back] : Responding after 5482 ms
[Front] : received response from server. State : open
[Front] : doing some sync processing
**Request N. 1**
[Back] : received a request from the front
[Back] : Responding after 7489 ms
[Front] : received response from server. State : open
[Front] : doing some sync processing
**Request N. 2**
[Back] : received a request from the front
[Back] : Responding after 9627 ms
[Front] : received response from server. State : open
[Front] : doing some sync processing
**Request N. 3**
[Back] : received a request from the front
[Back] : Responding after 5806 ms
[Front] : received response from server. State : closed
[Front] : doing some sync processing
saving to csv :
current_request;state
1;open
2;open
3;open

问题2

你不能。

至少不使用FileSaver 因为它不支持按块写入块。 当您实例化Blob ,您必须准备好所有数据。 有一些库支持块,但它们或者用于服务器端(例如node.js),或者非常特定于浏览器。

检查: 将客户端生成的数据保存为JavaScript中的文件

注意 :

如果您尝试使用js在客户端的计算机中存储1M行csv,那么该体系结构可能出现问题。 因为这不是浏览器的常见用例。 客户端应该具有弱机器,因此接收处理过的,轻量级的,易于解析的信息。 就此而言,您可以在服务器端构建csv,它具有写入流文件的所有权限,以及良好的处理/内存容量。

演示:问题1

http://jsbin.com/rojutudayu/2/edit?html,js,console

演示:如何下载blob?

  <script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"> </script> <script> var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"}); saveAs(blob, "hello world.txt"); </script> 

问题1:

使用forkJoin 它将等待所有Observable完成。 当您与delay(5000)结合使用时,最短时间为5秒。 如果在5s之前没有返回API响应,它仍会等到结果返回( 演示

const stream1$ = of(1).pipe(
  delay(5000)
);

const intervalTime = Math.random() * 5000 + 5000

// replace with your API stream
const stream2$ = of(intervalTime).pipe(
  delay(intervalTime)
);

forkJoin(stream1$, stream2$)
  .subscribe(([_, s2]) => {
    console.log(s2);
  })

问题2:

如果文件很大,您应该让Web浏览器处理它。 最好将文件保存在服务器中,然后返回下载链接。 对于小文件,性能不是问题。 您可以将文件数据存储在RAM中,然后保存文件一次。

编辑:如果文件很大,FileSaver开发人员建议使用StreamSaver 你应该看看它

StreamSaver.js采用不同的方法。 您现在可以直接创建一个可写的流直接到文件系统(我不是在谈论chromes沙盒文件系统),而不是将数据保存在客户端存储或内存中

StreamSaver.js是在客户端保存流的解决方案。 它非常适合需要保存客户端创建的大量数据的webapp,其中RAM非常有限,就像在移动设备上一样。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM