VIDEO LESSON

JavaScript上級:Classで並列数を制御する非同期タスクキューを作るの解説動画

再生時間 8:28動画を別のタブで開く ↗
動画の字幕・文字起こしを読む

再生中の字幕を濃い黒で表示し、一番上に追従します。字幕の行を選ぶと、その位置から再生できます。

  1. 0:00こんにちは。
  2. 0:01ジャバスクリプト入門、実践連載の第七回、最終回です。
  3. 0:05今回作るのは、タスクキューという仕組みです。
  4. 0:09タスクキューは、たくさんの非同期処理を一度に全部は始めず、決めた数だけを同時に実行し、
  5. 0:14残りは順番に待たせるためのクラスです。
  6. 0:20前回学んだクラスと、前々回学んだプロミスを組み合わせます。
  7. 0:25失敗しても次の処理が止まらないこと、全部終わるまで待てることまで含めて、
  8. 0:29実務で使える形に仕上げましょう。
  9. 0:34なぜ並列数を制限するのでしょうか。
  10. 0:37例えば、百件のデータを一度に取得しようとして、百本の通信を同時に始めると、
  11. 0:42相手のサーバーに大きな負担がかかります。
  12. 0:47自分の側でも、回線やメモリーが圧迫されて、かえって全体が遅くなることがあります。
  13. 0:54そこで、同時に実行するのは最大で二件まで、のように上限を決めます。
  14. 1:00上限の中で順番に処理すれば、速さと安定さを両立できます。
  15. 1:05画像のアップロードや、たくさんのエーピーアイ呼び出しなど、実務でよく出会う場面です。
  16. 1:13処理を少しずつ流すことで、一件ごとの失敗の影響も小さく抑えられます。
  17. 1:19焦って全部を広げるより、結果として早く終わることも多いのです。
  18. 1:25実装の前に仕様を決めます。
  19. 1:27一番大切なのは、キューへ渡すものが、実行済みのプロミスではなく、呼び出すと処理を始める関数だという点です。
  20. 1:35もし先に通信を呼んでしまうと、登録した時点で処理が始まってしまい、開始のタイミングをキューが制御できません。
  21. 1:43ルールはほかにもあります。
  22. 1:45同時実行数は上限以下に保つこと、待機中の関数は登録順に始めること、あるタスクが失敗しても後続は続けること。
  23. 1:53そして、受付を終了した後は新規登録を断り、全部終わったらそれを知らせることです。
  24. 1:59仕様を先に固定しておくと、実装中に迷ったときの判断基準になります。
  25. 2:03クラス全体の形を見ます。
  26. 2:05タスクキューが内部で持つ状態は五つです。
  27. 2:08同時実行の上限、現在実行中の件数、待機しているタスクの配列、全部終わるのを待つ人のリスト、
  28. 2:13そして受付を終了したかどうかの旗です。
  29. 2:17これらはすべてシャープ付きのプライベートフィールドにし、外から勝手に書き換えられないようにします。
  30. 2:24外へ公開するのは、実行中の件数を読むランニングと、待機件数を読むペンディングというゲッターだけです。
  31. 2:31状態を守ることで、上限を超える事故を防ぎます。
  32. 2:35ゲッターだけを公開する形は、前回の在庫クラスと同じ考え方です。
  33. 2:39外からは今の混み具合だけを見せて、仕組みそのものは触らせない、という線引きが大切です。
  34. 2:45コンストラクターでは、同時実行数の上限を受け取ります。
  35. 2:50省略した場合は二件です。
  36. 2:52ここでも前回と同じく、作る時点での検証が大切です。
  37. 2:56値が安全な整数でない場合や、一未満の場合は、レンジエラーを投げて作成自体を止めます。
  38. 3:02上限がゼロだと何も進まなくなりますし、小数や文字では制御が壊れてしまうからです。
  39. 3:09また、受付終了後に登録しようとしたときに返す専用のエラー、キュークローズドエラーも別クラスとして用意し、
  40. 3:14失敗の理由が分かるようにします。
  41. 3:20上限をコンストラクターで一度だけ決めて、後から変えられないようにしている点もポイントです。
  42. 3:27途中で上限が変わると、実行中の数え方が曖昧になってしまうからです。
  43. 3:32タスクの登録はアドメソッドです。
  44. 3:35まず受付が終了していないかを確認し、終了していれば専用エラーで拒否します。
  45. 3:41次に、渡されたものが関数かどうかを確かめ、関数でなければタイプエラーで拒否します。
  46. 3:47問題がなければ、タスクと、その結果を伝えるためのリゾルブとリジェクトをひとまとめにして待機列の末尾へ入れます。
  47. 3:56そして、すぐにドレインという内部メソッドを呼び、空き枠があれば開始を試みます。
  48. 4:02アド自体は、タスクごとの結果を受け取るためのプロミスを返す点も覚えてください。
  49. 4:08待機列には関数そのものだけでなく、結果を届けるための窓口も一緒に入れておきます。
  50. 4:15こうすることで、終わったタスクから順に、呼び出し元へ結果を返せる仕組みになります。
  51. 4:21並列制御の心臓部が、ドレインというプライベートメソッドです。
  52. 4:26実行中の件数が上限より少なく、待機列にタスクが残っている間だけ、ループで先頭から一件ずつ取り出します。
  53. 4:33取り出すとき、実際にタスクを呼ぶ前に、まず実行中カウンターを一つ増やして枠を確保するのがコツです。
  54. 4:41枠を先に押さえないと、処理が始まるまでのすき間に、上限を超えて予約してしまう恐れがあります。
  55. 4:47取り出したタスクは、プロミスの連鎖の中で呼び出し、成功と失敗の両方の受け口を用意します。
  56. 4:54このループがあるからこそ、タスクが一件終わるたびに次が自動で補充される流れが作れます。
  57. 5:00開始の判断をドレイン一か所に集めているのが、この設計の分かりやすさです。
  58. 5:06エラー処理の工夫を見ます。
  59. 5:08タスクは空のプロミスからつないで呼び出すため、その場で例外を投げる同期エラーも、
  60. 5:13後から失敗する非同期エラーも、どちらも同じ失敗側の処理へ流れます。
  61. 5:19成功した場合も失敗した場合も、最後は必ずフィニッシュを呼びます。
  62. 5:24フィニッシュでは実行中カウンターを一つ減らし、ドレインを呼んで次のタスクを開始します。
  63. 5:30さらに、実行中も待機中もゼロになれば、オンアイドルで待っていた人たちを全員解決します。
  64. 5:37枠の解放を一か所に集めることで、数え漏れを防ぎます。
  65. 5:41もし成功時だけ枠を解放して失敗時に解放し忘れると、キューはだんだんと詰まってしまいます。
  66. 5:47だからこそ、両方の道で同じ後始末を通る形にしているのです。
  67. 5:52実行例です。
  68. 5:53上限を二件にして、エーからイーまで五件のタスクを登録します。
  69. 5:57ビーは非同期で失敗するタスク、ディーはその場で例外を投げるタスクです。
  70. 6:03登録し終えたらクローズを呼んで受付を終え、結果はプロミスオールセトルドで受け取ります。
  71. 6:10結果は、成功、失敗、成功、失敗、成功の順になります。
  72. 6:14失敗が混ざっても残りが止まらないことが分かります。
  73. 6:18最後にオンアイドルを待つと、実行中も待機中もゼロになった状態で、きれいに締めることができます。
  74. 6:26開始の順番は登録順ですが、終わる順番は処理時間によって変わります。
  75. 6:31短い処理が先に終わることもあるので、結果の並びと完了のタイミングは分けて考えてください。
  76. 6:39注意点を三つ挙げます。
  77. 6:40一つ目は、アドが返したプロミスを放置しないことです。
  78. 6:44オンアイドルはキューが空になったことを知らせるだけで、各タスクの成功や失敗まで受け取ってはくれません。
  79. 6:52結果は必ず自分で受け止めてください。
  80. 6:55二つ目は、クローズの意味です。
  81. 6:57クローズは新規受付の終了であり、待機中や実行中の処理を消したり止めたりはしません。
  82. 7:04三つ目はデッドロックです。
  83. 7:06上限一件のキューで、実行中のタスクが同じキューへ追加して完了を待つと、空き枠が永遠に生まれず止まってしまいます。
  84. 7:14依存関係は事前に整理しましょう。
  85. 7:17テストをするときは、同時実行数の最大値が上限を超えていないか、失敗の後に後続が始まっているかも合わせて確認すると安心です。
  86. 7:26実運用へ広げる前に知っておきたい点で締めます。
  87. 7:31この実装にキャンセル機能はありません。
  88. 7:35通信を途中で止めたい場合は、アボートシグナルをタスクへ渡すなど、タスク側と協力する設計が要ります。
  89. 7:45また、待機件数に上限がないため、無制限に登録するとメモリーを圧迫します。
  90. 7:52大量登録には受付制限も必要です。
  91. 7:55設計面では、キューは順番と実行枠だけを管理し、実際の仕事は関数として受け取ります。
  92. 8:04仕事の種類が増えても、キューの子クラスを増やす必要はありません。
  93. 8:10これが役割分離の利点です。
  94. 8:13変数、関数、プロミス、クラスが一つにつながりました。
  95. 8:18連載はこれで完結です。
  96. 8:21お疲れさまでした。
  97. 8:22まずは上限を変えて動きの違いを確かめてみてください。
日本語字幕をダウンロード(WebVTT)

この動画で学べること

JavaScriptのClassとPromiseで同時実行数を制限するタスクキューを実装。同期例外、非同期の失敗、完了待ち、受付終了、デッドロックの注意点を解説します。

解説記事・サンプルコードを読む:JavaScript上級:Classで並列数を制御する非同期タスクキューを作る →