我有一个Angular 4服务,它使用Web worker为缓冲区中排队的文件块发送http请求。如果Web工作者以http错误状态代码响应,则Web工作者不应处理进一步的请求。以下链接说明了我想要实现的目标....
我已经实现了一种方法,该方法使用Observables将响应有效负载通知给订户。源代码包含在本文末尾。
当我运行代码时,会报告以下输出(N.B为了快速,我设置Web工作人员在收到http响应状态200时强制执行错误响应)。
creating message for chunk 0
Posting actual message with id 0
creating message for chunk 1
Posting actual message with id 1
creating message for chunk 2
Posting actual message with id 2
creating message for chunk 3
Posting actual message with id 3
creating message for chunk 4
Posting actual message with id 4
Completed processing message
Manager create document completed
Web worker sending chunk 0/5
Web worker sending chunk 1/5
Web worker sending chunk 2/5
Web worker sending chunk 3/5
Web worker sending chunk 4/5
Application error received from worker in main thread. Notifying subscriber
Completed observing worker response, closing worker
Upload error {"status":200,"message":"OK"}
Application error received from worker in main thread. Notifying subscriber
Completed observing worker response, closing worker
Upload error {"status":200,"message":"OK"}
Application error received from worker in main thread. Notifying subscriber
Completed observing worker response, closing worker
Upload error {"status":200,"message":"OK"}
Application error received from worker in main thread. Notifying subscriber
Completed observing worker response, closing worker
Upload error {"status":200,"message":"OK"}
Application error received from worker in main thread. Notifying subscriber
Completed observing worker response, closing worker
Upload error {"status":200,"message":"OK"}
在上面的输出中,所有http请求都是从Web worker发送的。随后,主线程接收响应的通知。
如何确保Web工作者请求和响应同步发生,即请求 - >等待回应 - >请求 - >等待回应?
谢谢你的考虑。
/**
* This is the main body of the worker process
* It receives a blob slice for upload and sends post to upload using
* multipart/form
* When completed it sends reponse message back to parent incorporating status
* and response text of the url state
*/
onmessage = (message: MessageEvent) => {
let type = message.data.type;
let id = message.data.id;
let payload = message.data.payload;
switch (type) {
case 'UPLOAD': {
let xhr : XMLHttpRequest = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if(xhr.readyState == XMLHttpRequest.DONE) {
let response = {type: type, id: id, payload: {status: xhr.status, message: xhr.responseText}, undefined};
if (xhr.status == 200) {
response.type = 'error';
}
self.postMessage(response, undefined);
}
};
xhr.open('POST', payload.url, true /*async must be true when using multipart*/);
let formData: FormData = new FormData ();
formData.append ('chunk', payload.chunk);
formData.append ('chunkid', payload.chunkid.toString());
formData.append ('documenttypeid', payload.documenttypeid.toString());
formData.append ('totalchunks', payload.totalchunks.toString());
formData.append ('taskid', payload.ticketid.toString());
console.log ('Web worker sending chunk %d/%d', payload.chunkid, payload.totalchunks);
xhr.send(formData);
}
}
};
import { Headers } from '@angular/http';
import { Http } from '@angular/http';
import { Injectable } from '@angular/core';
import { NgZone } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Request } from '@angular/http';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { Response } from '@angular/http';
import { ResponseContentType } from '@angular/http';
import { RequestMethod } from '@angular/http';
import { RequestOptions } from '@angular/http';
import { Subject } from 'rxjs/Subject';
let DocumentUploadWorker = require('worker-loader!./documentupload.worker.ts');
interface WebWorkerMessage {
type: string;
payload: any;
id?: number;
}
class WebWorkerClient {
private _nextMessageId : number = 0;
/**
* Factory Method
*/
static create(zone: NgZone) : WebWorkerClient {
return new WebWorkerClient(new DocumentUploadWorker (), zone);
}
/**
* Constructor
*/
/**
* Spawn a worker to handle chunked file uploads
* Initialise a stream to store upload responses
*/
constructor (private _worker : Worker, private zone : NgZone) {}
/**
* postMessage<T> : Observable<T>
* Create an observable that upon subscription will notify the subscriber
* of worker message and error responses
*/
public postMessage<T> (type : string, payload : any) : Observable <T> {
let _self = this;
return new Observable (subscriber => {
const id = this._nextMessageId++;
// create event handlers
const onMessage = (response : MessageEvent) => {
const {type: responseType, id: responseId, payload: responsePayload} = response.data as WebWorkerMessage;
if (responseType === 'error' && id === responseId) {
_self.zone.run (() => {
console.log ('Application error received from worker in main thread. Notifying subscriber');
subscriber.error (JSON.stringify(responsePayload));
this._error.next (true);
});
}
else if (type === responseType && id === responseId) {
_self.zone.run(() => {
console.log('Main thread is notifying subscriber of response payload...');
subscriber.next(responsePayload);
subscriber.complete();
});
}
}
const onError = (error : ErrorEvent) => {
console.log ('Main thread has received an error from worker');
_self.zone.run (() => {
subscriber.error (error);
});
}
// initialise event handlers
_self._worker.addEventListener('message', onMessage);
_self._worker.addEventListener('error', onError);
// post the actual message
console.log ('Posting actual message with id %d', id);
_self._worker.postMessage ({type, id, payload});
// at completion remove event listeners
return () => {
console.log ('Completed observing worker response, closing worker');
_self._worker.removeEventListener('message', onMessage);
_self._worker.removeEventListener('error', onError);
};
});
}
}
public uploadBuffer (buffer : Observable<FileChunk>, ticketId, uploadUrl : string) : void {
let worker : WebWorkerClient = WebWorkerClient.create (this.zone);
buffer
.flatMap (chunk => this.createMessage (chunk, ticketId, uploadUrl)) // create a deferred message for each chunk
.subscribe (
(message) => {
let observeMessage = worker.postMessage<boolean>('UPLOAD',message).publishReplay(1);
observeMessage.subscribe (
(item) => { console.log ('Uploaded item %s', item); },
(error) => { console.log ('Upload error %s', error); },
() => {}
);
observeMessage.connect ();
},
(error) => {
console.log ('Error received creating message', error);
},
() => {
console.log ('Completed processing message');
}
);
}
/**
* createMessage (chunk : FileChunk, ticketId, uploadUrl : string)
* defer creation of a request message until subscription.
*/
public createMessage (chunk: FileChunk, ticketId : string, uploadUrl : string) : Observable<any> {
return Observable.defer (
() => {
const message = {
url: uploadUrl,
chunk: chunk.chunk,
chunkid: chunk.chunkId,
documenttypeid: chunk.documentType,
ticketid: ticketId,
totalchunks: chunk.totalChunks
};
console.log ('creating message for chunk %d', message.chunkid);
return Observable.of (message);
}
);
}
在客户端使用concatMap提供有序响应:
let worker : WebWorkerClient = WebWorkerClient.create (this.zone);
buffer
.concatMap (chunk => this.createMessage (chunk, ticketId, uploadUrl))
.concatMap (message => worker.postMessage<boolean> ('UPLOAD',message))
.subscribe (
(message) => { console.log ('Message received is : ' + message); },
(error) => { console.log ('Error received creating message', error); },
() => { console.log ('Completed processing message'); }
);