Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Web Workerで○○する話

1,659 views

Published on

niigata.js #1

Published in: Technology
  • Be the first to comment

Web Workerで○○する話

  1. 1. Web Workerで〇〇する話 Niigata.js #1
  2. 2. 自己紹介 ushiboy プログラマ SPAアプリ開発が多め 使う言語はJavaScript 8割、pythonが2割みたいな感じ
  3. 3. Web Worker ご存知ですか or おぼえてますか
  4. 4. Web Workerについて三行で Webブラウザにマルチスレッドをもたらした。 Worker内だけの独自コンテキストをもつ。 Workerの外部とはメッセージングでやり取り。
  5. 5. ちょっと振り返り(JavaScriptの同期処理) 基本的にメインスレッド(UIスレッド)だけなので、注意が 必要だった。
  6. 6. 例1 Busyなループ function loopSync() { for (var i = 0; i < 1000000000; i++) { // busyなループ } console.log('finish!'); } document.getElementById('btn').addEventListener('click', function(evt) { loopSync(); // これが終わるまで何もかも待たされる }, false);
  7. 7. 例2 同期なHTTP通信 document.getElementById('btn').addEventListener('click', function(evt) { var xhr = new XMLHttpRequest(); xhr.open('GET', '/api/slow', false); // 第3引数のasyncフラグをオフにして"同期"通信に xhr.send(null); // レスポンスくるまで待たされる console.log(xhr.responseText); }, false);
  8. 8. そして出てくるダイアログ ※ブラウザに寄ります
  9. 9. なので ふつうは非同期なAPIを使う。 setTimeout駆使したり XMLHttpRequestは非同期で使う
  10. 10. Workerを使うと... function loopSync() { for (var i = 0; i < 1000000000; i++) { // busyなループ } console.log('finish!'); } loopSync(); document.getElementById('btn').addEventListener('click', function(evt) { new Worker('worker.js'); }, false); 重い部分をworker用にスクリプトごと分離 Workerを作って使う
  11. 11. これによって、ブラウザが怒らない ※繰り返しますが、ブラウザに寄ります
  12. 12. もうちょっと詳しく見ていきます というわけで...
  13. 13. Workerを使うためには ワーカー用のスクリプトのURI渡してインスタンスを生成する var worker = new Worker('worker.js'); このときのURIは同一生成ポリシーに従う必要がある。
  14. 14. メッセージのやりとり Worker UI postMessageで送る。 onmessageで受け取る。 worker.postMessage('start'); worker.onmessage = function(evt) { // MessageEvent console.log(evt.data); };
  15. 15. (もうちょっと)メッセージのやりとり Worker UI postMessageで送るデータは共有ではなくコピーされる。 onmessageで受けとったものはコピー。 worker.postMessage({ count: 1000 }); worker.onmessage = function(evt) { // MessageEvent console.log(evt.data); }; 同一のインスタンスを共有せずに双方で複製。
  16. 16. (番外)メッセージのやりとり postMessageで送るデータは共有ではなくコピーされる。 巨大なデータを送る場合にパフォーマンスを良くするため、 所有権の譲渡(Transferable Objects)という仕組みがある。 が、今回は省略... 参考 https://developer.mozilla.org/ja/docs/Web/Guide/Performance/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objec ts)
  17. 17. Workerの中 selfがグローバルスコープショートカット(UI側のwindow) windowやdocumentなど触れないものがある 詳しくは https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers 外部スクリプトはimportScriptsで読み込む WorkerがさらにWorkerを生成とかもできる。 self.onmessage = function(evt) { if (evt.data === 'start') { self.postMessage('fin'); } else if (evt.data === 'stop') { self.close(); } }; importScripts(‘foo.js’, ‘hoge.js’);
  18. 18. Workerの止め方 terminate呼ぶ。 ワーカー自身が内部からclose。 worker.terminate(); self.close();
  19. 19. Worker内でのエラー ErrorEventになるのでonerrorで拾える。 worker.onerror = function(evt) { // ErrorEvent console.log(evt.message); // “Uncaught Error:/(^o^)\” console.log(evt.lineno); // 12 };
  20. 20. Workerの種類 Dedicated Worker : 今まで見てきたやつ Shared Worker : 複数のスクリプトから共有できるやつ Service Worker : オフラインアプリにするときに役に立つやつ Chrome Worker : Firefox限定(アドオン用?) Audio Worker : オーディオ処理用(らしい)
  21. 21. ブラウザの対応状況(デスクトップ系) 細かい機能レベルになるともっと分かれる模様 参考 https://developer.mozilla.org/ja/docs/Web/Guide/Performance/Using_web_workers#Browser_compatibility Internet Explorer 10.0+ Firefox 3.5+ Chrome 4+ Opera 10.6+ Safari 4+
  22. 22. ちなみにpolyfillがあるっぽい が、そもそもpolyfillでどうにかなるものではない... 参考: https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#web-workers ホントそれ
  23. 23. Web Worker 使ってますか?
  24. 24. ちなみに私は...
  25. 25. まったく使ってない\(^o^)/ 自分の対象範囲だと重い処理やるケースない... サーバーとJSONでお話するのがほとんど… このままではタイトル詐欺になってしまう…
  26. 26. というわけで、ここから本題
  27. 27. ここでもう一度図をみてみる じーっと見ていると... Worker UI
  28. 28. Fluxと似てますね ※強引な気がするのは気のせいです。
  29. 29. というわけで この辺りをまるっとワーカーに入れてみる
  30. 30. Web Workerで(雑に)Fluxする お題:カウンターアプリ
  31. 31. WARNING!!!!! ここからコードがEcmaScript2015になりま す
  32. 32. UI側 function Counter(props) { const { count, plus, minus } = props; return ( <div> <div>{count}</div> <button onClick={plus}>+</button> <button onClick={minus}>-</button> </div> ); } Counterコンポーネントを用意
  33. 33. UI側 import React from 'react'; class App extends React.Component { constructor(props) { super(props); this.state = {}; this.worker = props.worker; this.worker.onmessage = evt => { this.setState(evt.data); }; } /** 右へ続く -> **/ /** 続き **/ render() { const { count } = this.state; return ( <Counter count={count} plus={this.handlePlus.bind(this)} minus={this.handleMinus.bind(this)} /> ); } /** 次のスライドへ続く **/ ControllerViewとしてAppを用意
  34. 34. UI側 /** 前のスライドからの続き **/ handlePlus() { this.worker.postMessage({ type: 'UPDATE_COUNT', payload: { value: 1 } }); } /** 右へ続く -> **/ /** 続き **/ handleMinus() { this.worker.postMessage({ type: 'UPDATE_COUNT', payload: { value: -1 } }); } } アプリケーションコンテナを用意
  35. 35. UI側 import { render } from 'react-dom'; render( <App worker={new Worker('back.js')} />, document.getElementById('app') ); アプリの起動部分
  36. 36. Worker側 const state = { count: 0 }; onmessage = evt => { postMessage(store(state, evt.data)); }; // initialize postMessage(state); function store(state, action) { const { type, payload } = action; switch (type) { case 'UPDATE_COUNT': return updateCount(state, payload); default: return state; } } function updateCount(state, payload) { const { value } = payload; state.count += value; return state; }
  37. 37. やってみた感じ 一方通行のデータフローの制限が自然に生まれる メッセージングのデータは複製されるので、UI側に渡すときに自分でステート をイミュータブルにしなくて良い Immutable.jsとかでやらなくて良い (雑すぎてDispatcher端折っちゃった)
  38. 38. 同じようなこと考えてる人いるっぽい 参考 https://medium.com/@nsisodiya/flux-inside-web-workers-cc51fb463882#.8apt7vhfh
  39. 39. まとめ Workerの中ならば必ずしも非同期じゃなくてもよい。 状況に応じて同期・非同期を選択できる。 Workerを積極的に取り入れたアーキテクチャ時代が来る(かもしれない)

×