FIVETEESIXONE

JavaScript なかなかいい

February 09, 2018

あー引っ越しの準備が忙しい。忙しいと、テスト前になぜか部屋を掃除したくなる的なアレで、普段気乗りがしないことをやりたくなりますね。

と、いうわけで!JavaScript!できればあまりお近づきにならず微妙な距離感で接しながら、できればいつのまにか消えててくれたらいいなあ…なんて思ってたこの人!をやっていくことにしたのでした。

で、何日か JS だけでコード書いてたんですけど…あれ?…ん…?JavaScript、なんだ、悪くないぞ…?って。

ES2015, 2016, 2017...

まずですね、此度の JS をやっていきのきっかけとして、ちと今いくつかこれからの技術選定とかをやっているんですが、おそらくどれもアクセススパイクに強いものが求められるということがあって、サーバーサイドの実装言語としては Go とか Kotlin とか、まあなんかいくつか候補を考えてたんですが、その中に Node.js もあって、どうせその素振りしないといけないからなぁ、ってのもありました。

なので JS といってもブラウザでギュンギュンとかじゃなくてターミナルで Node.js を触っていただけなんですが、なんていうか、Babel でトランスパイルとかそういうのを何にも考えなくてもいきなり ES2017 までの機能をネイティブですぐ使えるというのがまずよかったですね… JS やろう!という気持ちは往々にして webpack.config.js と格闘しているうちに全て萎えて泡と消えてしまうものですからね。

Node.js 9.x 以降であれば ES2017 までの機能がすべて使えます。

async/await

JS といえば Callback Hell、Callback Hell といえば JS というくらい、かねてよりバニラで非同期処理がサクッと書ける能力を手に入れていたのと引き換えとして、これまで JS が忌み嫌われてきた一番の理由はたぶんこの Callback Hell というやつで疑いないでしょう。わざわざ例示するまでもないかと思います。

さて、そこに ES2017 で登場したのが async/await です。これを使うと、非同期処理を以下のように書けるようになります:

async function asyncCall() {
  let result1 = await somethingTakesLong1();
  let result2 = await somethingTakesLong2();
  return result1 + result2;
}

asyncCall();

async を付けて宣言した関数の中では await が使えます。プログラムは await を付けて実行した非同期処理の実行が終わるまで待つので、これだけで同期的に処理を記述することが可能です。

なるほど、楽ですね〜、すばらしい!

これだけで JavaScript のことが好きになっちゃいそうですね。だって、これこんなに簡潔なのにちゃんと非同期処理なんですよ。ってか非同期処理じゃないとブロッキングしちゃうんで意味ないですからね。そうなんです、これは非同期処理なんです。は〜、ということは、これは同期処理じゃないんですね。

と、さっきから謎のトートロジーをこねてますが、この asyncCall() の実行結果は何になるかわかりますか?言い変えると asyncCall() の戻り値は何になる?JS を使う人ならもし async/await を知らなくてもわかると思います。

この関数の戻り値は Promise オブジェクトです!

Promise

Promise!出た!何を隠そう僕は Promise が大好きです!

まず名前がかっこいい、知らない人がいきなり見るとマジで突然 Promise とかいうプログラミングっぽくなさのあるワードが登場したのかわからない。「約束…ですかね?」それは確かに正しい。今すぐかつて黄色い看板だった消費者金融にお金を借りて過払い請求したくなるほど正しい、だがよくわからない。

Promise と (ちと乱暴だけど) 同じ概念を指すものとしてFuture というものがある。未来。実はこちらの方がイメージとしてはわかりやすい。

Future (Promise) は、未来に手に入る (約束されている) 、ということを意味する。こう聞くと言葉通りだ。てかこれは本当にそのままで、もっと具体的に言うと「今はないけど手に入る」という文脈でオブジェクトを包んだものだ。

非同期処理/並行処理の結果はその処理が終わるまでは未確定である。未確定なものをどう扱おうか?Promise を使う。Promise というのは「未来」をその状態のまま取り回すためのものなのだ。そして、Promise が未来であればいつかはその結果に辿り着く。それは成功した未来かもしれない (resolve) し、失敗した未来かもしれない (reject) 。

「未来を取り回す!」

プログラミングは楽しい、だが、それを通じて感動まで手に入れることはなかなかない。僕にとってそんな経験をした数少ない1つがこの Promise だった。だいたい厨二病に引き摺った人はこういうものが大好きだ。シュレディンガーの猫とかそういうやつ。まあそれは置いといて、Promise という、「その時点では不明な未来の文脈を持ったものを扱う」というのは結構衝撃的な発想に感じて俺の心は震えた。

Promise のことを知ったのが JS である人は多いと思う。僕もその一人だ。だが実際今では、サードパーティのライブラリも含めれば Promise が使えない言語を探す方が難しいくらいで、今回は ES2017 の async/await の話だけど Promise 自体は ES2015 ですでに JS にも標準に取り込まれている (だからさっき「async/await で Callbask Hell が解決!」とかいうのはもう半分嘘で、Promise によって解決されている) 。それどころか、後から知ったのだが、もう Promise の発想はすでに70年代の論文にあったという。それも感動だった。そんなに昔からあんの…!?すごくねえ!?

Promise のような、まるで外の世界と別の世界をプログラミングで扱える簡単な構文に閉じ込めるような (僕にとっては) 驚くべき仕組みに出会ったのは Promise が最初ではない。たぶん両者の時期的にはほとんど離れていないが、Swift の Optional もそれだった。僕に感動を与えてくれたもう1つ。

Optional も、「nil かもしれないし何かちゃんと値が入ってるかもしれない」という文脈でオブジェクトをラップした型だった。僕は Swift が発表されたときにそれにはじめて触れて、その発想と、それが Int? みたな簡潔な表現で「内側の、別の世界」を表現していることに感銘を受けた。

今なら、それが別に Swift の発明じゃないことも知っているし、それがファンクターやモナドと呼ばれるものの一種であり、非常に合理的な発想で生み出された機構であることを知っているが、そんなことを知らない当時の自分は大興奮していた。いや、今でもその気持ちが別に薄れたわけではないが。

あれ?

…って。う、うーん、何だろう、全く書こうとした内容を違うことを筆が進むままにもうずっと続けているぞ…俺は ES2017 の新機能でこんなに便利!みたいなのを書くつもりで、そのつもりでこの下にはすでに「## var/let/const」とか「## iodash はもう要らない!」みたいな見出しもいくつか書いてあるのだが…

まあいいか…こういう感じになりました… おわり!

※ちょっと補足すると、Scala や Ruby の Conccurent Ruby gem のように Future と Promise が完全に別ものとして扱われることも多いです。