WEBVTT

1
00:00:00.000 --> 00:00:01.039
こんにちは。

2
00:00:01.039 --> 00:00:05.540
ジャバスクリプト入門、実践連載の第七回、最終回です。

3
00:00:05.540 --> 00:00:09.349
今回作るのは、タスクキューという仕組みです。

4
00:00:09.349 --> 00:00:14.717
タスクキューは、たくさんの非同期処理を一度に全部は始めず、決めた数だけを同時に実行し、

5
00:00:14.717 --> 00:00:20.084
残りは順番に待たせるためのクラスです。

6
00:00:20.084 --> 00:00:25.105
前回学んだクラスと、前々回学んだプロミスを組み合わせます。

7
00:00:25.105 --> 00:00:29.606
失敗しても次の処理が止まらないこと、全部終わるまで待てることまで含めて、

8
00:00:29.606 --> 00:00:34.108
実務で使える形に仕上げましょう。

9
00:00:34.108 --> 00:00:37.184
なぜ並列数を制限するのでしょうか。

10
00:00:37.184 --> 00:00:42.341
例えば、百件のデータを一度に取得しようとして、百本の通信を同時に始めると、

11
00:00:42.341 --> 00:00:47.498
相手のサーバーに大きな負担がかかります。

12
00:00:47.498 --> 00:00:54.736
自分の側でも、回線やメモリーが圧迫されて、かえって全体が遅くなることがあります。

13
00:00:54.736 --> 00:01:00.708
そこで、同時に実行するのは最大で二件まで、のように上限を決めます。

14
00:01:00.708 --> 00:01:05.774
上限の中で順番に処理すれば、速さと安定さを両立できます。

15
00:01:05.774 --> 00:01:13.374
画像のアップロードや、たくさんのエーピーアイ呼び出しなど、実務でよく出会う場面です。

16
00:01:13.374 --> 00:01:19.527
処理を少しずつ流すことで、一件ごとの失敗の影響も小さく抑えられます。

17
00:01:19.527 --> 00:01:25.136
焦って全部を広げるより、結果として早く終わることも多いのです。

18
00:01:25.136 --> 00:01:27.087
実装の前に仕様を決めます。

19
00:01:27.087 --> 00:01:35.039
一番大切なのは、キューへ渡すものが、実行済みのプロミスではなく、呼び出すと処理を始める関数だという点です。

20
00:01:35.039 --> 00:01:43.141
もし先に通信を呼んでしまうと、登録した時点で処理が始まってしまい、開始のタイミングをキューが制御できません。

21
00:01:43.141 --> 00:01:45.091
ルールはほかにもあります。

22
00:01:45.091 --> 00:01:53.043
同時実行数は上限以下に保つこと、待機中の関数は登録順に始めること、あるタスクが失敗しても後続は続けること。

23
00:01:53.043 --> 00:01:59.045
そして、受付を終了した後は新規登録を断り、全部終わったらそれを知らせることです。

24
00:01:59.045 --> 00:02:03.996
仕様を先に固定しておくと、実装中に迷ったときの判断基準になります。

25
00:02:03.996 --> 00:02:05.713
クラス全体の形を見ます。

26
00:02:05.713 --> 00:02:08.576
タスクキューが内部で持つ状態は五つです。

27
00:02:08.576 --> 00:02:13.155
同時実行の上限、現在実行中の件数、待機しているタスクの配列、全部終わるのを待つ人のリスト、

28
00:02:13.155 --> 00:02:17.735
そして受付を終了したかどうかの旗です。

29
00:02:17.735 --> 00:02:24.747
これらはすべてシャープ付きのプライベートフィールドにし、外から勝手に書き換えられないようにします。

30
00:02:24.747 --> 00:02:31.902
外へ公開するのは、実行中の件数を読むランニングと、待機件数を読むペンディングというゲッターだけです。

31
00:02:31.902 --> 00:02:35.194
状態を守ることで、上限を超える事故を防ぎます。

32
00:02:35.194 --> 00:02:39.630
ゲッターだけを公開する形は、前回の在庫クラスと同じ考え方です。

33
00:02:39.630 --> 00:02:45.784
外からは今の混み具合だけを見せて、仕組みそのものは触らせない、という線引きが大切です。

34
00:02:45.784 --> 00:02:50.125
コンストラクターでは、同時実行数の上限を受け取ります。

35
00:02:50.125 --> 00:02:52.054
省略した場合は二件です。

36
00:02:52.054 --> 00:02:56.074
ここでも前回と同じく、作る時点での検証が大切です。

37
00:02:56.074 --> 00:03:02.827
値が安全な整数でない場合や、一未満の場合は、レンジエラーを投げて作成自体を止めます。

38
00:03:02.827 --> 00:03:09.258
上限がゼロだと何も進まなくなりますし、小数や文字では制御が壊れてしまうからです。

39
00:03:09.258 --> 00:03:14.725
また、受付終了後に登録しようとしたときに返す専用のエラー、キュークローズドエラーも別クラスとして用意し、

40
00:03:14.725 --> 00:03:20.191
失敗の理由が分かるようにします。

41
00:03:20.191 --> 00:03:27.426
上限をコンストラクターで一度だけ決めて、後から変えられないようにしている点もポイントです。

42
00:03:27.426 --> 00:03:32.732
途中で上限が変わると、実行中の数え方が曖昧になってしまうからです。

43
00:03:32.732 --> 00:03:35.268
タスクの登録はアドメソッドです。

44
00:03:35.268 --> 00:03:41.133
まず受付が終了していないかを確認し、終了していれば専用エラーで拒否します。

45
00:03:41.133 --> 00:03:47.632
次に、渡されたものが関数かどうかを確かめ、関数でなければタイプエラーで拒否します。

46
00:03:47.632 --> 00:03:56.349
問題がなければ、タスクと、その結果を伝えるためのリゾルブとリジェクトをひとまとめにして待機列の末尾へ入れます。

47
00:03:56.349 --> 00:04:02.531
そして、すぐにドレインという内部メソッドを呼び、空き枠があれば開始を試みます。

48
00:04:02.531 --> 00:04:08.713
アド自体は、タスクごとの結果を受け取るためのプロミスを返す点も覚えてください。

49
00:04:08.713 --> 00:04:15.053
待機列には関数そのものだけでなく、結果を届けるための窓口も一緒に入れておきます。

50
00:04:15.053 --> 00:04:21.552
こうすることで、終わったタスクから順に、呼び出し元へ結果を返せる仕組みになります。

51
00:04:21.552 --> 00:04:26.046
並列制御の心臓部が、ドレインというプライベートメソッドです。

52
00:04:26.046 --> 00:04:33.685
実行中の件数が上限より少なく、待機列にタスクが残っている間だけ、ループで先頭から一件ずつ取り出します。

53
00:04:33.685 --> 00:04:41.025
取り出すとき、実際にタスクを呼ぶ前に、まず実行中カウンターを一つ増やして枠を確保するのがコツです。

54
00:04:41.025 --> 00:04:47.916
枠を先に押さえないと、処理が始まるまでのすき間に、上限を超えて予約してしまう恐れがあります。

55
00:04:47.916 --> 00:04:54.506
取り出したタスクは、プロミスの連鎖の中で呼び出し、成功と失敗の両方の受け口を用意します。

56
00:04:54.506 --> 00:05:00.947
このループがあるからこそ、タスクが一件終わるたびに次が自動で補充される流れが作れます。

57
00:05:00.947 --> 00:05:06.340
開始の判断をドレイン一か所に集めているのが、この設計の分かりやすさです。

58
00:05:06.340 --> 00:05:08.304
エラー処理の工夫を見ます。

59
00:05:08.304 --> 00:05:13.818
タスクは空のプロミスからつないで呼び出すため、その場で例外を投げる同期エラーも、

60
00:05:13.818 --> 00:05:19.331
後から失敗する非同期エラーも、どちらも同じ失敗側の処理へ流れます。

61
00:05:19.331 --> 00:05:24.165
成功した場合も失敗した場合も、最後は必ずフィニッシュを呼びます。

62
00:05:24.165 --> 00:05:30.661
フィニッシュでは実行中カウンターを一つ減らし、ドレインを呼んで次のタスクを開始します。

63
00:05:30.661 --> 00:05:37.157
さらに、実行中も待機中もゼロになれば、オンアイドルで待っていた人たちを全員解決します。

64
00:05:37.157 --> 00:05:41.084
枠の解放を一か所に集めることで、数え漏れを防ぎます。

65
00:05:41.084 --> 00:05:47.731
もし成功時だけ枠を解放して失敗時に解放し忘れると、キューはだんだんと詰まってしまいます。

66
00:05:47.731 --> 00:05:52.112
だからこそ、両方の道で同じ後始末を通る形にしているのです。

67
00:05:52.112 --> 00:05:53.086
実行例です。

68
00:05:53.086 --> 00:05:57.954
上限を二件にして、エーからイーまで五件のタスクを登録します。

69
00:05:57.954 --> 00:06:03.634
ビーは非同期で失敗するタスク、ディーはその場で例外を投げるタスクです。

70
00:06:03.634 --> 00:06:10.612
登録し終えたらクローズを呼んで受付を終え、結果はプロミスオールセトルドで受け取ります。

71
00:06:10.612 --> 00:06:14.832
結果は、成功、失敗、成功、失敗、成功の順になります。

72
00:06:14.832 --> 00:06:18.889
失敗が混ざっても残りが止まらないことが分かります。

73
00:06:18.889 --> 00:06:26.516
最後にオンアイドルを待つと、実行中も待機中もゼロになった状態で、きれいに締めることができます。

74
00:06:26.516 --> 00:06:31.872
開始の順番は登録順ですが、終わる順番は処理時間によって変わります。

75
00:06:31.872 --> 00:06:39.012
短い処理が先に終わることもあるので、結果の並びと完了のタイミングは分けて考えてください。

76
00:06:39.012 --> 00:06:40.712
注意点を三つ挙げます。

77
00:06:40.712 --> 00:06:44.730
一つ目は、アドが返したプロミスを放置しないことです。

78
00:06:44.730 --> 00:06:52.611
オンアイドルはキューが空になったことを知らせるだけで、各タスクの成功や失敗まで受け取ってはくれません。

79
00:06:52.611 --> 00:06:55.392
結果は必ず自分で受け止めてください。

80
00:06:55.392 --> 00:06:57.710
二つ目は、クローズの意味です。

81
00:06:57.710 --> 00:07:04.046
クローズは新規受付の終了であり、待機中や実行中の処理を消したり止めたりはしません。

82
00:07:04.046 --> 00:07:06.055
三つ目はデッドロックです。

83
00:07:06.055 --> 00:07:14.709
上限一件のキューで、実行中のタスクが同じキューへ追加して完了を待つと、空き枠が永遠に生まれず止まってしまいます。

84
00:07:14.709 --> 00:07:17.182
依存関係は事前に整理しましょう。

85
00:07:17.182 --> 00:07:26.608
テストをするときは、同時実行数の最大値が上限を超えていないか、失敗の後に後続が始まっているかも合わせて確認すると安心です。

86
00:07:26.608 --> 00:07:31.320
実運用へ広げる前に知っておきたい点で締めます。

87
00:07:31.320 --> 00:07:35.212
この実装にキャンセル機能はありません。

88
00:07:35.212 --> 00:07:45.249
通信を途中で止めたい場合は、アボートシグナルをタスクへ渡すなど、タスク側と協力する設計が要ります。

89
00:07:45.249 --> 00:07:52.624
また、待機件数に上限がないため、無制限に登録するとメモリーを圧迫します。

90
00:07:52.624 --> 00:07:55.902
大量登録には受付制限も必要です。

91
00:07:55.902 --> 00:08:04.300
設計面では、キューは順番と実行枠だけを管理し、実際の仕事は関数として受け取ります。

92
00:08:04.300 --> 00:08:10.856
仕事の種類が増えても、キューの子クラスを増やす必要はありません。

93
00:08:10.856 --> 00:08:13.519
これが役割分離の利点です。

94
00:08:13.519 --> 00:08:18.845
変数、関数、プロミス、クラスが一つにつながりました。

95
00:08:18.845 --> 00:08:21.098
連載はこれで完結です。

96
00:08:21.098 --> 00:08:22.942
お疲れさまでした。

97
00:08:22.942 --> 00:08:28.268
まずは上限を変えて動きの違いを確かめてみてください。
