使用Angular2 / RxJS读取缓冲响应

时间:2016-10-02 19:19:56

标签: angular rxjs observable angular2-http

我正在建立一个从后端读取数据的网站。该数据即时计算并以缓冲方式发送回客户端。即一旦计算出第一个块,就会将其发送到客户端,然后计算下一个块并将其发送给客户端。整个过程发生在同一个HTTP请求中。客户端不应该等待完成响应完成,而是在发送后立即自己处理每个块。通常可以使用XHR进度处理程序(例如How to get progress from XMLHttpRequest)来消费此类响应。

如何使用RxJS和Observables在Angular2中使用HttpModule消耗这样的响应?

编辑:peeskillet在下面给出了一个非常详细的答案。此外,我还进行了一些挖掘,找到了feature request for the HttpModule of AngularStackOverflow question with another approach on how to solve it

1 个答案:

答案 0 :(得分:5)

注意:以下答案仅为POC。它旨在教育Http的体系结构,并提供简单的工作POC实现。我们应该查看XHRConnection的来源,了解在实施此项目时您应该考虑的其他内容。

当试图实现这一点时,我认为没有任何方法可以直接进入XHR。似乎我们需要提供使用Http所涉及的一些组件的自定义实现。我们应该考虑的三个主要组成部分是

  • Connection
  • ConnectionBackend
  • Http

HttpConnectionBackend作为其构造函数的参数。在发出请求时,例如getHttp会创建与ConnectionBackend.createConnection的连接,并返回Observable Connection属性(从{{1}返回的属性1}})。在最简化的(简化)视图中,它看起来像这样

createConnection

因此,了解这种架构,我们可以尝试实现类似的东西。

对于class XHRConnection implements Connection { response: Observable<Response>; constructor( request, browserXhr) { this.response = new Observable((observer: Observer<Response>) => { let xhr = browserXhr.create(); let onLoad = (..) => { observer.next(new Response(...)); }; xhr.addEventListener('load', onLoad); }) } } class XHRBackend implements ConnectionBackend { constructor(private browserXhr) {} createConnection(request): XHRConnection { return new XHRConnection(request, this.broswerXhr).response; } } class Http { constructor(private backend: ConnectionBackend) {} get(url, options): Observable<Response> { return this.backend.createConnection(createRequest(url, options)).response; } } ,这是POC。为简洁而省略了导入,但在大多数情况下,所有内容都可以从Connection导入,@angular/http可以从Observable/Observer导入。

rxjs/{Type}

以下是我们刚订阅XHR export class Chunk { data: string; } export class ChunkedXHRConnection implements Connection { request: Request; response: Observable<Response>; readyState: ReadyState; chunks: Observable<Chunk>; constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) { this.request = req; this.chunks = new Observable<Chunk>((chunkObserver: Observer<Chunk>) => { let _xhr: XMLHttpRequest = browserXHR.build(); let previousLen = 0; let onProgress = (progress: ProgressEvent) => { let text = _xhr.responseText; text = text.substring(previousLen); chunkObserver.next({ data: text }); previousLen += text.length; console.log(`chunk data: ${text}`); }; _xhr.addEventListener('progress', onProgress); _xhr.open(RequestMethod[req.method].toUpperCase(), req.url); _xhr.send(this.request.getBody()); return () => { _xhr.removeEventListener('progress', onProgress); _xhr.abort(); }; }); } } 事件。由于progress会发出整个连接文本,我们只需要XHR.responseText来获取块,然后通过substring发出每个chuck。

对于Observer,我们有以下(没什么了不起的)。同样,所有内容都可以从XHRBackend;

导入
@angular/http

对于@Injectable() export class ChunkedXHRBackend implements ConnectionBackend { constructor( private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions, private _xsrfStrategy: XSRFStrategy) {} createConnection(request: Request): ChunkedXHRConnection { this._xsrfStrategy.configureRequest(request); return new ChunkedXHRConnection(request, this._browserXHR, this._baseResponseOptions); } } ,我们会对其进行扩展,添加Http方法。如果需要,可以添加更多方法。

getChunks

可以在Http source

中找到@Injectable() export class ChunkedHttp extends Http { constructor(protected backend: ChunkedXHRBackend, protected defaultOptions: RequestOptions) { super(backend, defaultOptions); } getChunks(url, options?: RequestOptionsArgs): Observable<Chunk> { return this.backend.createConnection( new Request(mergeOptions(this.defaultOptions, options, RequestMethod.Get, url))).chunks; } } 方法

现在我们可以为它创建一个模块。用户应直接使用mergeOptions代替ChunkedHttp。但是,由于不要尝试覆盖Http令牌,如果需要,您仍然可以使用Http

Http

我们导入@NgModule({ imports: [ HttpModule ], providers: [ { provide: ChunkedHttp, useFactory: (backend: ChunkedXHRBackend, options: RequestOptions) => { return new ChunkedHttp(backend, options); }, deps: [ ChunkedXHRBackend, RequestOptions ] }, ChunkedXHRBackend ] }) export class ChunkedHttpModule { } 因为它提供了我们需要注入的其他服务,但是如果我们不需要,我们不希望重新实现这些服务。

要测试只是将HttpModule导入ChunkedHttpModule。另外要测试我使用了以下组件

AppModule

我设置了一个后端端点,它每隔半秒就会在10个块中吐出@Component({ selector: 'app', encapsulation: ViewEncapsulation.None, template: ` <button (click)="onClick()">Click Me!</button> <h4 *ngFor="let chunk of chunks">{{ chunk }}</h4> `, styleUrls: ['./app.style.css'] }) export class App { chunks: string[] = []; constructor(private http: ChunkedHttp) {} onClick() { this.http.getChunks('http://localhost:8080/api/resource') .subscribe(chunk => this.chunks.push(chunk.data)); } } 。这就是结果

enter image description here

某处似乎有一个错误。只有 9 :-)。我认为这与服务器端有关。