Node.js の非同期処理を関数合成で繋げてみる - 連続コールバック対策
Node.js でプログラミングする際の課題は、非同期処理のコールバックが多段になって分かり難くなってしまう点だと思います。(下記は CoffeeScript の例)
func1 引数・・・, (err, 処理結果1) -> if err? エラー処理 else ・・・ func2 引数・・・, (err, 処理結果2) -> if err? エラー処理 else ・・・ func3 引数・・・, (err, 処理結果3) -> if err? エラー処理 else ・・・
対策はいろいろあると思いますが(id:fits:20120415 で使った TameJS もその一つ)、今回は id:fits:20101213 で扱ったような関数合成を適用する方法を模索してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120617/
コールバックを繋げて関数合成
なんとなく、コールバック結果を繋げて関数合成すれば使い道ありそうな気がしました。
つまり、"関数1" のコールバック結果を引数にして "関数2" を呼び出し、そのコールバック結果を引数にして "関数3" を呼び出し・・・というような処理を関数合成で作り出せればと。
そこで、試しに CoffeeScript を使って関数合成する処理を実装してみました。
compose.coffee
# 関数をコールバックで繋げて処理する無名関数を返す exports.compose = (funcs...) -> (args, callback) -> cb = genCallback callback if funcs?.length > 0 for i in [(funcs.length - 1)..0] cb = genCallback cb, funcs[i] cb null, args genCallback = (callback, func) -> (err, res) -> if err? callback err else if func? func res, callback else callback null, res
compose 関数は、渡された関数を入れ子で繋げて処理する無名関数(args と callback が引数)を返します。
ただし、compose に渡す関数の引数も (args, callback) で統一しておく必要があります。
処理としては、compose f1, f2 で以下のような無名関数が作られるイメージです。(cb の部分は関数実行時に毎回構築される点に注意)
compose f1, f2 で作成される無名関数のイメージ
(args, callback) -> cb = (err, res0) -> if err? callback err else f1 res0, (err, res1) -> if err? callback err else f2 res1, (err, res2) -> if err? callback err else callback null, res2 cb null, args
動作確認
動作確認のため、上記で作成した compose 関数を使ったサンプルを作成・実行してみます。
sample.coffee
c = require './compose' # 関数1 f1 = (args, cb) -> console.log "f1 : #{args}" cb null, [args, "aaa"] # 関数2 f2 = (args, cb) -> console.log "f2 : #{args}" cb null, 10 # 関数3 f3 = (args, cb) -> console.log "f3 : #{args}" if args is 10 cb null, "done" else cb 'error3' # 関数4 f4 = (args, cb) -> console.log "f4 : #{args}" cb 'error4' # 関数1, 2, 3 を合成 cf1 = c.compose f1, f2, f3 # 関数1, 2, 3 の合成関数を実行 cf1 'test', (err, res) -> console.log "result1 : #{err}, #{res}" console.log '-----' cf2 = c.compose f1, f4, f2, f3 cf2 'test', (err, res) -> console.log "result2 : #{err}, #{res}" console.log '-----' cf3 = c.compose f2, f1, f3 cf3 'test', (err, res) -> console.log "result3 : #{err}, #{res}" console.log '-----' cf4 = c.compose f4, f2 cf4 'test', (err, res) -> console.log "result4 : #{err}, #{res}" console.log '-----' cf5 = c.compose f2, f1 cf5 'test', (err, res) -> console.log "result5 : #{err}, #{res}" console.log '-----' cf6 = c.compose() cf6 'test', (err, res) -> console.log "result6 : #{err}, #{res}" console.log '-----' cf7 = c.compose f1 cf7 'test', (err, res) -> console.log "result7 : #{err}, #{res}" console.log '-----' # 合成関数 cf1, cf5, cf7 を合成 cf8 = c.compose cf1, cf5, cf7 cf8 'test8', (err, res) -> console.log "result8 : #{err}, #{res}"
実行結果は以下の通り。一応、動作しているようです。
実行結果
> coffee sample f1 : test f2 : test,aaa f3 : 10 result1 : null, done ----- f1 : test f4 : test,aaa result2 : error4, undefined ----- f2 : test f1 : 10 f3 : 10,aaa result3 : error3, undefined ----- f4 : test result4 : error4, undefined ----- f2 : test f1 : 10 result5 : null, 10,aaa ----- result6 : null, test ----- f1 : test result7 : null, test,aaa ----- f1 : test8 f2 : test8,aaa f3 : 10 f2 : done f1 : 10 f1 : 10,aaa result8 : null, 10,aaa,aaa