CureApp開発者ブログ

アプリで治療する未来を創造するCureApp, Inc. のエンジニアブログです

実践JavaScript Promise

Promise

JavaScriptの非同期処理のスタイルも収束に向かっています。標準に取り込まれるPromiseが 今後は主流になるものと考えています。

弊社のJavaScriptでは非同期処理の99%以上がPromiseを返す関数です。 Promiseを返す関数だけの世界では、もはや値を返す関数のごとく自由になれます。

弊社製のライブラリでは100%非同期処理はPromiseで返していますので、気兼ねなくご利用ください。

さて、Promiseの使い方についてはJavaScript Promiseの本などに詳しいですが、 実際フルに使ってみて、いくつかパターンがあったり、ハマりどころがあったりしたので、それを書いていきます。

Promise.all() は常に検討しよう

fry(egg).then =>
    microwave(hamburg)
.then =>
    makeLocomoco(egg, hamburg)

fry(egg)に5分、 microwave(hamburg)に5分かかっていたらmakeLocomoco()までに合計10分かかりますが、

Promise.all([
    fry(egg)
    microwave(hamburg)
]).then =>
    makeLocomoco(egg, hamburg)

こうするとmakeLocomoco()まで5分で済みます。

コントローラで複数のファイルアクセスとネットワークアクセスなどが必要になる場合、それらに依存関係がなければ このようにするとよいです。

Promise.race() とキャッシュ

Promise.race([
   getFromCache(id)
   getFromNetwork(id).then (result) => saveToCache(id, result)
]).then (result) =>

Promise.race()は片方のみにエラーが起きても、thenで繋いでくれますので、

  • キャッシュがなければネットワークにアクセスして値を返す
  • キャッシュがあればそれを返す
  • ネットワークに接続できなくてもよい

といった要件を、上記コードのみで実現可能です。

Promiseの連鎖を途中でやめたい正常処理

目玉焼き作って出来合いのハンバーグを温めるけど目玉焼き失敗したらハンバーグでいい。 目玉焼き成功したらロコモコにする。

Promise.all([
    fry(egg)
    microwave(hamburg)
]).then =>
    if not egg.isFriedWell()
        return hamburg # 目玉焼き失敗したのでハンバーグでいいや

    # ロコモコをつくる
    makeSauce()

.then (sauce) =>
    mix(egg, hamburg, sauce)

これは期待通りに動作しません。 Promiseはインデントが深くなるのを防ぐことができるのですが、 最後の2行をその高さで書いてしまうと、return hamburgした行の続きとしても実行されてしまいます。

これを防ぐためには、

  1. インデントが深くなるのを妥協
  2. ハンバーグをつくるのも正常処理だけどthrowする
  3. ロコモコを創る部分を別関数にする

の3つがあります。 私の思想では3がよいと感じています。(今回ぐらいなら1も可)

makeLocomoco = (egg, hamburg) ->
    makeSauce().then (sauce) =>
        mix(egg, hamburg, sauce)

Promise.all([
    fry(egg)
    microwave(hamburg)
]).then =>
    if not egg.isFriedWell()
        return hamburg # 目玉焼き失敗したのでハンバーグでいいや
    else
        return makeLocomoco(egg, hamburg)

setTimeout() もPromiseに

wait = (msec) ->
    new Promise  (resolve) ->
        setTimeout(resolve, msec)

これで

wait(3000).then => # 3秒待って...
    # do something

ができます。応用すると、

ユーザの体感のため最低1秒は待ちたい

Promise.all([
    getFromNetwork()
    wait(1000)
]).then (result) =>
    [ val ] = result
    # do something

ということもできます。

Promiseを返しても、返さなくてもいいパターンに

Promise.resolve( fn() ).then (result) =>

fnがPromiseを返す関数でも、普通に値を返す関数でもワークします。

まとめ

Promiseで統一された非同期処理の世界はとても快適です。 CureAppで一緒に快適なJavaScript開発をしませんか?