-
Notifications
You must be signed in to change notification settings - Fork 0
WebWorker
WebWorker.js は、WebWorkers の機能を薄くラップしたものです。
Main Thread 側で機能する lib/WebWorker.js と、Worker 側で機能する lib/WorkerThread.js で構成されています。
Main Thread | Worker
----------------------------------------+----------------------------------------
worker = new WebWorker(source,callback) |
| worker = new WorkerThread(callback)
|
worker.request(requestMessage) ---|--> callback(requestMessage)
|
callback(err,responseMessage) <--|--- worker.respnse(responseMessage)
- WebWorker から WorkerThread 側に送るメッセージをリクエストと呼びます
- WorkerThread から WebWorker 側に返すメッセージをレスポンスと呼びます
- リクエストは WebWorker#request で生成します。
生成したリクエストは new WorkerThread(callback) の callback に届きます - レスポンスは WorkerThread#respnse で生成します。
生成したレスポンスは new WebWorker(, callback) の callback に届きます - リクエストを追跡するための情報を Ticket と呼びます。
チケットは、リクエスト時に発行され、レスポンスの完了で消費され、使用済みになります。 - リクエストとレスポンスの状況を確認するには、
- new WebWorker(,, { verbose: true }) または DevTools で WebWorker.dump() を実行します
[WebWorkers の勘所](WebWorkers Crux)も参考にしてください。
以下は、WebWorker からのリクエストと WorkerThread からのレスポンスの基本的な流れです。
Main Thread | Worker | |
---|---|---|
初 | worker = new WebWorker(source, callback) | worker = new WorkerThread(callback) |
1 | worker.request(body) | |
2 | callback(body) { ... }が呼ばれる | |
3 | worker.response(body) でレスポンスを返す | |
4 | callback(err, body) { ... } が呼ばれる |
WebWorker#on(method, methodCallback) を実行しておくと、method に対するレスポンスを methodCallback で受け取る事ができます。
同様に、WorkerThread#on(method, methodCallback) を実行しておくことで、method に対するリクエストを methodCallback で受け取る事ができます。
Main Thread | WorkerGlobalScope | |
---|---|---|
初 | worker = new WebWorker(source) | worker = new WorkerThread) |
on | worker.on("Foo", methodCallback) | worker.on("Bar", methodCallback) |
1 | worker.request(body, { method: "Bar" }) | |
2 | methodCallback(body) { ... }が呼ばれる | |
3 | worker.response(body, { method: "Bar" }) でレスポンスを返す | |
4 | methodCallback(err, body) { ... } が呼ばれる |
new WebWorker(source:URLString|JavaScriptFragmentString, callback:Function = null, options:Object = {}) は、WebWorker のインスタンスを生成し返します。
source には Worker を生成するためのソースコードを指定します。 callback は WorkerThread#response のタイミングでコールバックされます。
callback は callback(err:Error|null, body:Any, param:Object):void の形でコールバックされます。
body には WorkerThread#response(body:Any, options:Object) で渡された body がそのまま渡されます。
Worker でエラーが発生した場合は、err に ErrorObject が格納されています。
options には以下の値を指定できます。
property:type = default | value |
---|---|
name:String = "" | WebWorker のインスタンスに設定する名前を指定します。省略した場合は1から始まる数字になります |
origin:String = "" | WorkerGlobalScopre の self.origin の値を指定します |
inline:Boolean = false | source に inline Worker 用の文字列を指定した場合に true にします |
import:URLStringArray = []
|
WorkerThread の初期化時に、一緒に読み込むスクリプトのURLを配列で指定します |
verbose:Boolean = false | verbose mode にします。 リクエストとレスポンスに関するログを出力します |
function WebWorker(source, // @arg URLString|JavaScriptFragmentString - new Worker(source) or inline worker source
callback, // @arg Function = null - callback(err:Error, body:Any, param:Object):void
options) { // @arg Object - { name, origin, import, inline, verbose }
// @options.name String = "" - Worker name.
// @options.origin URLString = "" - set WorkerGlobalScope.origin.
// @options.import URLStringArray = [] - importScripts(...) files.
// @options.inline Boolean = false - use inline WebWorker.
// @options.verbose Boolean = false - verbose mode.
// @desc init Worker session.
//{@dev
$valid($type(source, "String"), WebWorker, "source");
$valid($type(callback, "Function|omit"), WebWorker, "callback");
$valid($type(options, "Object|null"), WebWorker, "options");
$valid($keys(options, "name|origin|import|inline|verbose"),
WebWorker, "options");
$valid($type(options.name, "String|omit"), WebWorker, "options.name");
$valid($type(options.origin, "String|omit"), WebWorker, "options.origin");
$valid($type(options.import, "Array|omit"), WebWorker, "options.import");
$valid($type(options.inline, "Boolean|omit"), WebWorker, "options.inline");
$valid($type(options.verbose,"Boolean|omit"), WebWorker, "options.verbose");
//}@dev
this._source = source;
this._callback = callback || null;
this._name = options["name"] || "";
this._origin = options["origin"] || "";
this._import = options["import"] || [];
this._inline = options["inline"] || "";
this._verbose = options["verbose"] || false;
//{@dev
$valid(WMURL.isValid(this._origin), WebWorker, "options.origin");
$valid(WMURL.isValid(this._import), WebWorker, "options.import");
//}@dev
this._on = {}; // { method: callback, ... }
this._blobURL = ""; // blob url for inline worker
this._worker = null; // new Worker(source) instance
this._workerID = ++_workerID;
this._requestID = 0;
this._name = this._name || this._workerID;
_tickets[this._name] = _tickets[this._name] || {};
}
var scripts = [
"../node_modules/uupaa.valid.js/lib/Valid.js",
"../node_modules/uupaa.task.js/lib/Task.js"
];
var worker = new WebWorker("./worker.import.js", function(err, body, param) {
if (err) {
;
} else {
if (body.result === "OK") {
//task.pass();
return;
}
}
//task.miss();
}, { "import": scripts, verbose: true });
worker.request();
importScripts("../lib/WorkerThread.js");
var worker = new WorkerThread(function(body, param) {
new Task(1, function(err) {
worker.response({ "result": "OK" });
}).pass();
});
WebWorker#request(body:Any, options:Object = {}):TicketString は、 必要に応じて Worker を生成し、 WebWorker ⇔ WorkerThread 間でリクエストを追跡するための各種情報と共に、Worker にメッセージを飛ばします、 リクエストを追跡するためのユニークな文字列(TicketString)を返します。
options には以下のキーワードを指定できます
key:type = default | value |
---|---|
options.transfer:Array = null | transferable object を指定します |
options.ticket:String = "" | 外部で生成した ticket を指定する場合に使用します。通常は使用しません |
options.method:String = "" | WorkerThread#on(method, callback) を呼び出す場合に指定します |
options.reply:String = "" | WorkerThread#response からのレスポンスを受けとるメソッドを強制します。通常は使用しません |
WebWorker#request が生成するリクエスト(requestStructure)の詳細です。
key:type = default | value |
---|---|
requestStructure.init:Boolean = false | Worker の初期化が必要なタイミング(初回リクエスト)で true になります |
requestStructure.origin:String = "" | options.origin の値です |
requestStructure.import:URLStringArray = []
|
options.import の値です |
requestStructure.ticket:String | リクエストを追跡するためのIDです |
requestStructure.method:String = "" | options.method の値です |
requestStructure.reply:String = "" | options.reply の値です |
requestStructure.cancel:Boolean = false | WebWorker#cancel を実行した場合に true になります |
requestStructure.body:Any | body の値です |
function WebWorker_request(body, // @arg Any - request body.
options) { // @arg Object = {} - { transfer, ticket, method, reply }
// @options.transfer Array = null - transferable object
// @options.ticket TicketString = ""
// @options.method MethodNameString = ""
// @options.reply MethodNameString = ""
// @ret TicketString
// @desc open and request Worker session.
//{@dev
$valid($type(options, "Object|omit"), WebWorker_request, "options");
$valid($keys(options, "transfer|ticket|method|reply"), WebWorker_request, "options");
//}@dev
options = options || {};
var transfer = options["transfer"] || null;
var ticket = options["ticket"] || _makeTicket(this._workerID, ++this._requestID); // {{WORKER_ID}}.{{REQUEST_ID}}
var method = options["method"] || "";
var reply = options["reply"] || "";
//{@dev
$valid($type(options.transfer, "Array|omit"), WebWorker_request, "options.transfer");
$valid($type(options.ticket, "String|omit"), WebWorker_request, "options.ticket");
$valid($type(options.method, "String|omit"), WebWorker_request, "options.method");
$valid($type(options.reply, "String|omit"), WebWorker_request, "options.reply");
//}@dev
var initialized = !!this._worker;
var requestStructure = {
"init": initialized ? 0 : 1, // false/true -> 0/1
"origin": initialized ? "" : this._origin,
"import": initialized ? "" : this._import.join(","), // ArrayString -> CommaJointString
//"error": "",
"ticket": ticket,
"method": method,
"reply": reply,
"cancel": 0,
"body": body
};
if (!initialized) {
_initWorker(this);
}
_updateTicketState(this._name, ticket, _STATE_REQUEST, method);
//{@verbose
if (this._verbose && global["console"]) {
var logMessage = "--\x3e WebWorker(" + this._name + ")#request(" + ticket + ", " + method + ", " + reply + ")";
if (console.group) {
console.group(logMessage);
} else {
console.log(logMessage);
}
}
//}@verbose
// send request to worker
if (transfer) {
this._worker["postMessage"](requestStructure, transfer);
} else {
this._worker["postMessage"](requestStructure);
}
return ticket;
}
WebWorker#request(, options) の options.transfer に transferable object を指定すると、Worker 側に ArrayBuffer の参照を渡す事ができます。
詳しくは、 Transferable Objects を参照してください。
transferableObjects に指定したオブジェクトは所有権が Worker 側に移り、以後 UI Thread からはアクセスできなくなります(配列を渡した場合は、配列のlengthが 0 になります)。
// Transferable Objects を指定した WebWorker#request
var worker = new WebWorker("./worker2.js", function(err, body, param) {
});
var bigTypedArray = new Uint8Array(1024 * 1024 * 4); // 4MB
worker.request(bigTypedArray.buffer, { transfer: [ bigTypedArray.buffer ] });
importScripts("../lib/WorkerThread.js");
var worker = new WorkerThread(function(body, param) {
// body は bigTypedArray.buffer です。4MB のデータが格納されています。
// 以下のようにすると、 Main Thread 側に送り返すも事ができます。
worker.response(body, { transfer: [ body ] });
});
WebWorker#on(method:String, methodCallback:Function):this は、method に対応する methodCallback を登録します。
WebWorker#on("Foo", methodCallback);
の状態で、 WorkerThread#response(body, { method: "Foo" }) を実行すると、methodCallback が呼ばれます。
コールバックの引数は methodCallback(err:Error|null, body:Any, param:Object):void になります。
method は複数登録できません。複数登録すると、最後に登録した methodCallback が呼ばれます。
function WebWorker_on(method, // @arg MethodNameString
methodCallback) { // @arg Function - methodCallback(err:Error, body:Any, param:Object):void
// @ret this
//{@dev
$valid($type(method, "MethodNameString"), WebWorker_on, "method");
$valid($type(methodCallback, "Function"), WebWorker_on, "methodCallback");
//}@dev
this._on[method] = methodCallback;
return this;
}
WebWorker#off(method):this は、WebWorker#on を取り消します。
function WebWorker_off(method) { // @arg MethodNameString
// @ret this
//{@dev
$valid($type(method, "MethodNameString"), WebWorker_off, "method");
//}@dev
delete this._on[method];
return this;
}
WebWorker#cancel(ticket:String):Boolean は、WorkerThread#on("cancel", callback) を呼び出します。
ticket には WebWorker#request が返す ticket を指定します。このメソッドは、既に発行済みのリクエストをキャンセルするために使用します。
function WebWorker_cancel(ticket) { // @arg TicketString
// @ret Boolean
//{@dev
$valid($type(ticket, "String"), WebWorker_cancel, "ticket");
//}@dev
if (_tickets[this._name][ticket]["state"] === _STATE_REQUEST) {
_updateTicketState(this._name, ticket, _STATE_CANCEL, "cancel");
//{@verbose
if (this._verbose && global["console"]) {
console.log("--\x3e WebWorker(" + this._name + ")#cancel(" + ticket + ")");
}
//}@verbose
var requestStructure = { "ticket": ticket, "cancel": 1 };
this._worker["postMessage"](requestStructure);
return true;
}
return false;
}
WebWorker#close() は、Worker を終了させ、インスタンスの終了処理を行います。
WebWorker#close() 実行後に、WebWorker#request で再度リクエストを行うことも可能です。
close 後に、request を実行した場合は再度初期化が行われ、新しい Worker が生成されます。
function WebWorker_close() { // @desc close all Workers session
var that = this;
that._worker.removeEventListener("message", that);
that._worker.removeEventListener("error", that);
that._worker["terminate"]();
that._worker = null; // [!][GC]
_updateTicketState(that._name, 0, _STATE_CLOSED, "");
if (that._inline) {
_URL["revokeObjectURL"](that._blobURL); // [!] GC
that._blobURL = "";
}
}
WebWorker#inbox(task:Task, body:Any, id:String):void は、Message.js と連携して機能し、WorkerThread#inbox メソッドを呼び出します。
function WebWorker_inbox(task, // @arg Task
body, // @arg Any
id) { // @arg String - instance id
// @desc Message.js over WebWorker.js
//{@dev
$valid($type(task, "Task"), WebWorker_inbox, "task");
$valid($type(id, "String"), WebWorker_inbox, "id");
//}@dev
var that = this;
var ticket = _makeTicket(that._workerID, ++that._requestID);
var unique = "__REPLY__" + ticket; // make unique string
that["on"](unique, function(err, body) {
that["off"](unique);
if (!err) {
task["set"](id, body);
}
task["done"](err);
});
return that["request"](body, { "ticket": ticket,
"method": "inbox", "reply": unique });
}
new WorkerThread(callback:Function) は、WorkerThread のインスタンスを生成します。
callback は WebWorker#request のタイミングでコールバックされます。
callback は callback(body:Any, param:Object):void の形でコールバックされます。
body には WebWorker#request(body:Any, options:Object) で渡された body がそのまま渡されます。
WorkerThread#on(method:String, methodCallback:Function):this は、method に対応する methodCallback を登録します。
WorkerThread#on("Bar", methodCallback);
の状態で、 WebWorker#request(body, { method: "Bar" }) を実行すると、methodCallback が呼ばれます。
コールバックの引数は methodCallback(body:Any, param:Object):void になります。
method は複数登録できません。複数登録すると、最後に登録した methodCallback が呼ばれます。
function WorkerThread_on(method, // @arg MethodNameString
methodCallback) { // @arg Function - methodCallback(body:Any, param:Object):void
// @ret this
this._on[method] = methodCallback;
return this;
}
WorkerThread#off(method):this は、WorkerThread#on を取り消します。
function WorkerThread_off(method) { // @arg MethodNameString
// @ret this
delete this._on[method];
return this;
}
WorkerThread#response(body:Any, options:Object = {}):TicketString は、レスポンスを送信します。
WorkerThread#response が生成するリクエスト(responseStructure)の詳細です。
key:type = default | value |
---|---|
responseStructure.error:String = "" | エラー文字列です |
responseStructure.ticket:String | リクエストを追跡するためのIDです |
responseStructure.method:String = "" | options.method の値です |
responseStructure.body:Any | body の値です |
function WorkerThread_response(body, // @arg Any - response data
options) { // @arg Object = {} - { transfer, ticket, method, error, reply }
// @options.transfer Array = null - Transferable Objects.
// @options.ticket TicketString = ""
// @options.method MethodNameString = ""
// @options.error ErrorObject = null
// @options.reply MethodNameString = ""
// @ret TicketString
options = options || {};
var transfer = options["transfer"] || null;
var ticket = options["ticket"] || this._ticket;
var method = options["reply"] || // [!] The reply take precedence over method.
options["method"] || "";
var error = options["error"] || null;
var responseStructure = {
//"init": 0,
//"origin": "",
//"import": "",
"error": error ? error.message : "",
"ticket": ticket,
"method": method,
//"reply": "",
//"cancel": 0,
"body": body
};
if (transfer && !error) {
global["postMessage"](responseStructure, transfer);
} else {
global["postMessage"](responseStructure);
}
return ticket;
}