Diceこの広告は、90日以上更新していないブログに表示しています。Workersと戦うJavaScriptこのブログには(リンク記事も含め)約300件ほどの記事がたまっています。左側にある最新記事一覧やタグ一覧はJavaScriptでTumblrのAPIを叩いてとってきているんですが、これがですね、流石に重過ぎるだろと。5兆回ぐらい言っているんですが、Tumblrにはタグ一覧を取得する機能がありません。初回読み込み時には20件ずつしこしことリクエストを発行、全件取得してから更に編集、表示と言うプロセスを踏んでいます。一連の動作はほとんどすべて同期処理で行われています。他のJavaScriptを読み込んだり、実行したり、HTMLのレンダリングもやっているスレッドでやらざるを得ないので、一瞬画面が固まったりするのは大体そのせいです。Workersと呼ばれる概念を使うことで可能だと言うことを最近知りました。Workersの基本的な使い方、ハック、そして敗北について書いていきます。Workersの使い方(基本)使い方は簡単のようで簡単ではありません。少なくとも、近年の各言語のような手厚いサポートは得られません。Workerオブジェクトを手に入れます。Worker("background.js");コンストラクタにはバックグラウンドで実行するタスクを記述したファイルパスを指定します。このファイルは同一生成元ポリシーを守る必要があります。これに違反するとSecurityErrorが発生してしまいます。オブジェクトを作成しただけではタスクは実行されません。Worker.postMessageを実行することでキューを送ります。background.jsの非同期実行worker.postMessage();タスクとして指定されたJavaScriptファイルはself.onmessageと言うプロパティにファンクションを設定しておくことでWorkerからのキューを受け取ることが出来ます。TODO:非同期処理};この時、Worker側からタスクへ引数を渡すことが出来ます。e.data;};JSONが送れるので大体どうにかなりますが、ファンクションとかクラスとかそう言うものは送れません。なんか値型っぽいものだけ送れる、みたいな認識でいいと思います。逆にタスク側からWorkerにpostMessageを発行することも出来ます。Workerにもonmessageプロパティを設定しておくことで、タスク側からのキューを受け取ることが出来ます。});勿論onmessageはaddEventListenerで代替可能です。postMessageで渡すオブジェクトは値をコピーして渡すので、あんまり重たいものを何度も繰り返し渡すのは性能的に好ましくありません。Objectsを使うと高速化できるらしいのですが、対応しているブラウザのバージョンが微妙に高いのが難点です。Workersの使い方(応用)Workerに指定されたタスク内で発生した例外は基本的に消滅します。握りつぶすでも、親スレッドに伝播するでもありません。消えてしまいますが、未処理の例外が発生した時点でタスクの実行は終了してしまいます。なれば当然WorkerへpostMessage出来ないので、終わったのか終わってないのかわからないまま終了していまいます。親スレッドに伝播させたい場合はWorkerにonerrorを設定しておきましょう。すれば親スレッドで受け取ることができます。(勿論これもaddEventListenerで代替可能です。});また、タスクからWorkerへは何度でもpostMessageを送りつけることができます。(逆もまた然りだが滅多にやることはないと思う。)これにより現在の進捗状況などを親スレッドに通知できます。タスクが完了したらWorkerはきちんとterminateしておきましょう。Workersの使い方はこんな感じです。コードをちょっと改良するだけで使えそうな気がしてきますが、そんなことはありません。既存のコードはよっぽど上手く設計していないと再利用できません。言うのも、タスク側は非常に多くの制約を課せられているからです。具体的には以下の機能以外のことはできません。navigatorの使用locationの使用(ただし読み取り専用)XMLHttpRequestの使用アプリケーションキャッシュの使用importScriptsによる外部jsファイルの読み込みWorkerの作成これはつまり、documentやwindowと言った、JavaScriptの根幹とも言える基本的な機能にアクセスできないと言うことです。DOM操作も出来ない(=JSONPも使えない)と考えていいです。「documentやwindowって別にJavaScriptの根幹機能じゃなくね?」と言う指摘を見かけたので訂正。「クライアントサイドJSにおいてほとんどどのような場所でも普通に使えると思ってしまいがちなwindow、またwindowを省略して呼べるメソッドやオブジェクト(alertとかdocumentとか)は使えません。」ただし、先述した通りnavigtorやlocation、他にもsetInterval、setTimeoutなどの一部機能は使用することができます。要は「別スレッドでUI(DOM)に触るな」ってことです。Workersで呼び出したタスク内のグローバルスコープは呼び出し元のグローバルスコープとは別物になると言うところです。Windowオブジェクトがないんだもの。具体的に言うと、以下のコードは実行できません。self.postMessage(self.hogehoge());};もしこれをどうしても実行したいと思ったら、background.jsでもhogehogeと言うファンクションを登録するしかありません。Workersで出来ることの中に「Workerの作成」がありました。タスク内で呼ばれたタスクも、やはりグローバルスコープは別物になります。self.postMessage(self.hogehoge());};じゃあbackgorund-2.jsにもhogehogeを登録する?ってなると、もう嫌になってきます。これぐらい単純な例ならそれもいいかもしれませんが、全ファイルに何度も同じようなメソッド・定数・クラスを登録していくと当然修正のコストが跳ね上がります。これを解決するための仕組みとして、importScriptsメソッドが用意されています。使い方は単純で、引数として読み込みたいJavaScriptのファイルのパスを渡すだけです。可変長引数的なアレなので、複数のファイルを一気に渡すこともできます。src=”./import.js”>と記述したようなものだと思えばOKです。importScriptsでは同一生成元ポリシーのルールを守る必要があります。(違反するとNetworkErrorが発生する。)もしクロスドメインで読み込みたいのであればXHRが使えるのでそれの結果をevalするしかないでしょう。面倒ですし、Access-Control-Allow-Originが設定されてなかったらやっぱり使えないんですが...。Workersにおける制約は適用されます。内部でwindowやdocumentに触っているとエラーになります。(これもNetworkErrorになる。)なのでjQueryは使えません。Ajaxの部分だけ使いたい場合は別のライブラリを探すか自分でXHRで実装することになります。JSONPもそのままでは使用できません。DOMの操作が出来ないからです。これもXHRで受け取った結果をevalすれば済む話ではありますが...。同一生成元ポリシーに違反しない限りはimportScriptsの引数にAPIのURIを渡すことで擬似JSONPを実行できます。動的にタスクを生成するWorkerのコンストラクタはセキュリティの観点から同一ドメインのURIしか受け付けてくれません。それだと流石に使いにくいです。Functionを受け取ってくれればいいのに、と思うので、そうしましょう。正確にはFunctionを基にBlobを作成Blobをwindow.URL.createObjectURLに食わせてURLを生成そのURLをWorkerに食わせると言う流れになります。Blobの生成に関してはこの解説が非常にわかりやすかったです。コンストラクタにはStringの配列、オプションとしてBlobのContent-Typeを指定します。FunctionはtoStirngすることでその定義を取得できるので、タスク内のonmessageで呼び出すようにするだけでOKです。Worker(brobUri);Blobは使い終わったらwindow.URL.revokeObjectURLで解放してあげるのが親切です。言うわけで、こんな仕組みがパパッと思いつくと思います。FWorker;})(this);このままだとimportScriptsが出来ないんですが、その辺はちょっと後回しにします。Blobに関する互換性問題を解消するこれだけで済むなら割と楽なんですが、Blobの対応ブラウザは結構まちまちです。現在は非推奨となっているBlobBuilderを使用すれば同じことが出来るので、もしBlobに対応していなかったらBlobBuilderを使うようにします。それともう一つ、どうにもならない問題としてIEではBlobのURIをWorkerのコンストラクタに指定出来ないと言うものがあります。「WorkerはサポートしているけどBlobはサポートしてない」と言ったクソブラウザのために、「渡されてきたFunctionのStringをevalするだけ」のjsファイルを用意しておきます。上記の問題はStackOverFlowのこの解説が非常に詳しいので、参考にしてみましょう。ついでにimportScriptsも出来るようにしてしまいます。}})(this);やむを得ず使用するjs(Delegetor)の中身はこんな感じです。そのままevalするとまたスコープが変わってしまい、importScriptsした意味がなくなるので一旦evalを別の変数に受け渡します。")");};後はこんな感じに使えばOKです。}}).execute("hoge");まとめとまぁ、こんな感じのことを一週間ほどやっていました。14.x以外ではテストしていないんですが...。Workers内でのXMLHttpRequestのノウハウは先人たちが色々やっているので特に紹介しません。私はこのあたりを参考にしました。JavaScriptな情報をあなたにここからは完全に余談になるのですが、上記の仕組みはこのブログでは使っていません。理由は単純で、Tumblrで使うjsは「static.tumblr.com」と言うドメインに置かれてしまうからです。(Delegatorを使ったWorkerを作成したりimportScriptsを呼び出す段階で同一生成元ポリシーに違反してしまう。)解決策として「static.tumblr.comにWorkerを呼び出すだけのHTMLをアップロードし、iframeで呼び出しておいてpostMessageでやりとりする」と言うものを思いついたんですが、そうなると何でもかんでもevalするFWorkerは巨大なセキュリティホールにしかなりません。言うわけで完全に普通のWorkerを使って実装しなきゃいけないので、まだまだ作成中です。[2014/08/29追記]static.tumblr.comにはHTMLをアップロードできませんでした。完全に万策尽きたので諦めようかと思います...。私はSQLを書くこと自体は別に嫌いじゃないんですが、ス...2014-01-28【JavaScript】JavaScriptでのクラスの作り方を考えるすっかりJavaScript漬けの毎日です。(1)はてなブログをはじめよう!outofmemさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。