非同期処理というものが必要な理由
Apache Http Serverは接続ごとにプロセスを生成するような仕組みです。このため、接続してくるクライアントがものすごく多くなると、メモリ領域を圧迫したり、レスポンス性能が壊滅的に低くなります。
この問題は、C10K問題(Client 10,000)と呼ばれ、これを解決するには、小さいリソースで多数のクライアントに対応できる仕組みが必要でした。
その具体的な解決策として出てきたのが、NginxやNode.jsです。今回はJavaScriptにおけるasync await
に着目しますので、Node.jsについて焦点を当てます。
Node.jsでは、多数の処理要求をシングルスレッドで(少ないリソースで)高速に捌けるよう、ノンブロッキングIOを実現するアーキテクチャが採用されています。このあたりの詳しい説明は「Node.jsでのイベントループの仕組みとタイマーについて」で学びましょう。
要求を捌いてくれるスレッドは1つだけなわけですから、仮に全ての処理が同期的に行われてしまうと、レスポンス性能が非常に低くなります。クライアントからの処理を1つずつ順番に完了させていくような感じになるわけで、DBアクセスやファイルアクセスのようなIO処理で生じる待ち時間はボーッと待つだけです。当然ながら、その待ち時間で他の処理を進めた方が好ましいでしょう。
つまり、ある処理でIO等の待ちが発生する場合、他の処理に速やかにバトンを渡してあげる必要があるのです。これを実現する手段こそが非同期処理です。
私たちの書くコードで、待ちが発生するような重い処理を呼び出す場合、「この重い処理が終わったら、このコールバック関数を呼んでね」という感じでコールバック関数を指定しますよね。これは同期的な流れをいったん断ち切り、他の処理にバトンを渡しているということです。
以上が、非同期処理というものが必要な理由です。
async awaitやPromiseが必要な理由
非同期処理をうまく使うことで他の処理にバトンを渡す、という大前提の方針のなかで私たちはコードを書いていくことになります。
とはいえ、他の処理にバトンを渡して、すぐに次の処理(次の行のコード)を実行して良いかというと、多くの場合そんなことはないはずです。
例えば、DBを参照する処理であれば、その次にDBから取得したデータを使って何らかの処理をするでしょう。DBを参照する処理が完了するまで、次に行ってはいけないのです。
ここで必要なのは、「他の処理にバトンを渡しながらも、自分のコードでは次に進まずに止まる」ということです。
これを実現するのがPromiseや、Promiseを使いやすくしたasync awaitです。
Promiseやasync awaitについては、「JavaScript Promiseの本」で学びましょう。
私がPromiseやasync awaitを知った時は、「非同期処理を活用すべき環境のなかで、無理やり同期処理を実現していて、何が嬉しいのか分からないな・・」と感じたものです。これは、Promiseやasync awaitで待っている間、他の処理もブロックされている、という誤解から生じた考えでした。
Node.jsのノンブロッキングIOという環境の中で、他の処理にバトンを渡しつつも、自分のコードの処理では次に進まずに、重い処理が終わるまでジッと待つ。このような器用なことを実現するために、Promiseやasync awaitがあるわけです。
AWS LambdaでのNode.js
AWS Lambdaでは言語としてJavaScript(Node.js)を選ぶことができます。
Lambdaは当然ながら基本的には1つの処理要求を受けて、単発で動作するものですから、上記で述べたような「多数のクライアントからの処理要求を捌く」といった状況にはありません。
クライアントが1つしかいないような状況ですから、純粋に自分の処理が同期的に進むようにコードを書けば良いでしょう。
DBアクセスのような時間のかかるIO処理を呼び出すときには、誰にバトンを渡すわけでもないですが、Promiseやasync awaitを使って自分の処理が同期的に進むようにコードを書く、ということですね。
何というか、ちょっと無意味さを感じちゃいますね。