この文書は
JavaScript Promiseに関する個人的なメモです。
Promise初心者の悩み
Promiseはチェーンでコールバックスパゲティを避けていい感じに書けます。
しかし、Promiseには、Promise.resolve()などの省略メソッドや、return値の自動Promiseラッピングの機構などなど見えない機構がもろもろ含まれています。
さらにサンプルごとにこれらの組み合わせが異なっていて、結果的にうまく書かないと、あれ?どうすればいいかわからないぞ?となったりしたので、自分なりに編み出した自分ルールをメモしときます。
失敗コード
初心者(自分)のコードと脳内はこんな感じでした。
# コールバックを受け付ける非同期関数
func1 = (f) ->
setTimeout () ->
f('result1')
, 500
func2 = (f) ->
setTimeout () ->
f('result2')
, 500
value = []
new Promise (resolve, reject) ->
func1 (result) ->
# ここはthenじゃないから引数がないのか
# チェーンの外のローカル変数valueに直接アクセスするか
# なんか気持ち悪いけどまあいいか・・・
value.push(result)
# ここはOK
resolve(value)
.then (arg) ->
func2 (result) ->
# ここはthenの引数でvalueにアクセスができる。
# なんか統一性がないなぁ・・・
arg.push(result)
# あれ?resolveできないぞ?
# func2のcallbackの中だからreturn Promise.resolve()とかやってもだめだ
# チェーンが切れる・・・
.then (arg) ->
# 呼ばれない
console.log(value)
問題点1:thenの中でresolveできない・・・
resolveしたいんですが、あれ?thenのなかからresolve呼べないの?となってます。
各種サンプルではreturn Promise.resolve()とやっているコードが出てきたりしますが、コールバックの中からはreturnができません。(コールバックの戻り値になってしまうため)
thenの中でコールバックが出てこなければPromise.resolve()でいいんですが、わざわざPromise使う理由はコールバックの連鎖をきれいに書きたいからなので・・・。
問題点2:チェーンの中から外側のローカル変数にアクセスしている・・・
まあいいんですが、結合度が高くてなんか気持ち悪い。
今のコード
今は下記のような書き方をするようにしています。
# コールバックを受け付ける非同期関数
func1 = (f) ->
setTimeout () ->
f('result1')
, 500
func2 = (f) ->
setTimeout () ->
f('result2')
, 500
Promise.resolve([])
.then (arg) ->
return new Promise (resolve, reject) ->
func1 (result) ->
arg.push(result)
resolve(arg)
.then (arg) ->
return new Promise (resolve, reject) ->
func2 (result) ->
arg.push(result)
resolve(arg)
.then (arg) ->
return new Promise (resolve, reject) ->
# 呼ばれる
console.log(arg)
resolve(arg)
自分ルールは下記の感じ。
- チェーンの始まりはPromise.resolveにしてチェーン内で引き回すパラメータを渡す
- すべてのthenの中でPromiseをnewしてreturnする
- Promiseでない値をreturnすることはしない
- コールバックパラメータの方のresolveで解決を明示する
- return Promise.resolve()は使わない
終わりに
この書き方に統一して下記の改善を得ました。
- コールバックの中からresolveが呼べる
- チェーンの外側の変数に直接アクセスしないでよくなり、結合度が下がる
でも毎回thenの中でnew Promiseを書くの面倒だな・・・。
thenのパラメータあたりにresolve, rejectが自動的に渡ってくればいいのに。
追記
- コメント欄にてアドバイスをいただき、promisifyすることでいい感じに書けるという情報を得ました
- promisifyすることで魔法のような現象がおきました
- 勝手に戻りがPromiseになる
- 自動でresolveされる
- しかもresolveの引数にはthenの戻り値が入ってくれる(=次のチェーンの引数になる)
- ひと手間かかりますが、これは素晴らしい
promisify版
# コールバックを受け付ける非同期関数
func1 = (f) ->
setTimeout () ->
f('result1')
, 500
func2 = (f) ->
setTimeout () ->
f('result2')
, 500
promisify = (f) ->
return ->
new Promise (resolve) ->
f(resolve)
funcP1 = promisify(func1);
funcP2 = promisify(func2);
Promise.resolve([])
.then (arg) ->
funcP1().then (result) ->
arg.push(result)
return arg
.then (arg) ->
funcP2().then (result) ->
arg.push(result)
return arg
.then (arg) ->
console.log(arg)