軽量マルチスレッドJavaScriptライブラリ「Thread.js」 1.0.0 リリース

 Thread.js 1.0.0をリリースしました。

 公式ページはこちら

Thread.jsとは

 WebWorkerAPIを、簡潔な構文で利用できるように考案したJavaScriptライブラリです。。

 使い方などの詳細は、Thread.jsをご参照頂くとして、本稿では、その設計意図や経緯などをご説明します。

経緯

 長らく、JavaScriptはウェブページ上の処理を一手に担っていました。しかし、重い処理を実行させると、ページがフリーズしたかのように応答しなくなることがあります。

 これは、JavaScriptが常にシングルスレッドで実行される言語であるため、ページの描画やスクロールなど、本来優先されるべき描画処理を行うだけの割り込みを発生させられないことが原因でした。

 回避策として、setTimeoutで擬似的に処理を他へ渡すことは出来ますが、setTimeoutはセットされたキューを順番に処理しているにすぎません。

 つまり、やはりsetTimeout内で重い処理を走らせようとすると、先述したことと同じような現象が起こってしまいます。

WebWorkerAPI

 そこで、新たに定義されたWebWorkerAPIを使います。

 最近の主要ブラウザには大抵実装されているAPIですが、実際にこれを使っているページは稀でしょう。

 上述したような、画面が応答しなくなってしまう問題は、シングルスレッドであるにも関わらず、無理やり重い処理をさせようとして発生したものでした。これを解決するには、setTimeout関数を駆使した複雑なコーディングが必要でした。

 今回利用する「WebWorkerAPI」はOSレベルで新しいスレッドを生成するため、たとえば素数の判定や、円周率の計算など、数々のごく重い処理をスマートに計算させることができます。

WebWorkerAPIの問題点

 WebWorkerAPIを使ったことのある方ならご存じだと思いますが、WebWorkerAPIには、functionを渡すことができません。つまり、いちいちjsファイルを外部定義するか、Blobオブジェクトを生成して渡すかのいずれかの手段しかありません。

 これは、単に別スレッドで進行する関数が欲しいだけのユーザーには、複雑で、煩雑な処理を書かなければいけないWebWorkerの利用を断念する充分な理由になり得ます。

 単に関数を実行させたいだけなのに、外部jsファイルを一から書く、もしくは、URL、Blobのオブジェクトを生成する処理を書くことは、全く理にかなったものではありませんでした。

 これを簡単に解決できるよう考案したのが、「Thread.js」です。

設計意図

 「シンプル」に尽きます。簡単で、学習の必要なく、誰でも気軽に使えるようなインターフェース設計を心掛けました。

簡潔なコード

 まずは、円周率計算のExampleをご覧いただくのが早いでしょう。Thread.js特有のコードはわずか3行です。

    var thread = new Thread(calcPi, "https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/4.0.0/bignumber.min.js");
    thread.once(2000).done(function(a){
        showResult(a);
    });

 ページにコードが掲載されていますが、円周率計算のメソッドは、いたってシンプルにガウス=ルジャンドル法をコードに落とし込んだだけです。

API Document

 Thread.jsのUsageをご確認頂ければすべて理解できる程度に簡素化したつもりですが、以下に記載しておきます。

Constructor(func, [depends])

new Thread(func, [depends])

 コンストラクタです。関数を元にThreadオブジェクトを生成します。

 渡された関数は、Thread.onceまたはThread.executeメソッドが実行されるまで実行されません。

引数 説明
func 新しいスレッドで処理を行う関数
[depends] StringまたはArray。依存ライブラリのフルパスを指定

once(arg1, arg2, …)

thread.once(arg1, arg2);

 Threadオブジェクトに登録されている関数を「一度だけ」安全に実行します。

 実行が終わった後、terminateメソッドが自動的に呼ばれ、内部のWorkerプロセス、URLオブジェクトなどをメモリから解放します。

引数 説明
args 関数に渡す引数

execute(arg1, arg2, …)

thread.execute(arg1, arg2);

 Threadオブジェクトに登録されている関数を実行します。実行が完了した後も、オブジェクトはメモリを占有し続けます。

 オブジェクトが必要なくなった時は、必ずThread.terminate()メソッドでメモリを明示的に開放してください。

 これは、状態を永久に更新し続けるメソッドや、ajaxなどの非同期処理を行う処理で有用です。

引数 説明
args 関数に渡す引数

terminate()

thread.terminate();

 Threadオブジェクトを即座に破棄します。スレッドが処理を行っている最中であっても、強制的にメモリ上からアンロードします。

 後述するイベントは、タイミングによってはいずれも永久に呼ばれなくなります。これを回避するには、alwaysなどのキューに登録するか、onceメソッドを利用してください。

引数 説明
(なし) (なし)

done(func), fail(func), always(func)

thread.done(function(args){});

 jQueryなどのDeferredオブジェクトと同様、処理成功(done)、失敗(fail)、進捗(progress)、全般(always)の振る舞いを定義するメソッドです。

 セットされた関数は、キューに格納され、上記いずれかのイベントが発行された際に、セットされた順番に呼び出されます。

 doneに登録された関数には、Threadコンストラクタで定義した関数のreturn値が渡されます。

 failに登録された関数には、Workerのエラーオブジェクトがそのまま渡されます。

引数 説明
func 処理完了時に呼ばれる関数

progress(func)

 Threadコンストラクタに渡す関数のローカルスコープ関数である、notifyメソッドにより呼び出されます。これはalwaysの対象にはなりません。

 進捗状況の報告に使われるメソッドですが、他のことにも使われます。たとえば、ajaxなどの非同期実行の戻り値をメインスレッドに報告できます。

 progress()のExample

引数 説明
func 処理完了時に呼ばれる関数、notifyの引数がそのまま渡される

開発にご協力頂けるみなさまへ

 GitHub Thread.jsは、どなたでも気軽にご参加いただけます。GitFlowに従い、まずはお気軽にPull Requestください。

 また、機能追加やご要望、バグの報告、ご質問等については、Issuesに投稿をお願いいたします。