async.waterfallとneo-async.angelFallの話
今回登場するモジュールはこちらです。
簡単に説明すると、Node.jsにおいて、callback地獄とif (err) return callback(err);
の多用を軽減して読みやすくするためのモジュールです。
async.waterfallについて
async.waterfallは、非同期な処理を逐次行っていく時に、メインに使うメソッドです。
使い方
var async = require('async'); // or 'neo-async' async.waterfall([ function(next) { next(null, 1); }, function(result, next) { // result に1が入る。 next(); } ], function(err) { // 途中でnext(err)すると、ここに飛んでerrが入る。 }
問題点
次のように、第2引数を返したり返さなかったりするメソッドがあったときに、その次の関数の受け方が定まらなくて、よくバグの原因になる。
function anAsyncMethod(foo, callback) { if (foo) { callback(null, 1); // [A] } else { callback(null); // [B] } } async.waterfall([ function(next) { var foo = false; anAsyncMethod(foo, next); }, function(result, next) { // [A] の場合はresultに値(1)が入るが、 // [B] の場合は入らない。(すなわち、resultにnextと呼ぶべき関数が入っている) next(); // TypeError: next is not a function } ], function(err) { });
回避方法はある(後述)ものの、面倒だし、
「値を返していない状態から、返すように変更する」ことがbreaking changeになるのも辛い。
同期関数だったり、非同期コールバックスタイルでも普通の使い方であれば、そうはならないのに。
回避方法
anAsyncMethodがコールバックへ引数渡してきたり渡さなかったりするから困るなー、でもanAsyncMethod自体は書き換えられないな、という時は、次のように回避する。
async.waterfall([ function(next) { var foo = false; anAsyncMethod(foo, function(err, result) { next(err, result); // 1つ渡すことを強制している }); }, function(result, next) { // ...
neo-async.angelFall
waterfallと似ているが、上述の問題が解決されたもの。
常に最後の引数にnext(と呼ぶべき関数)を渡す。
すなわち、関数の仮引数を、単に(next)
と書けば、前の関数が何を渡そうが無視されて、nextに関数が入る。
もし(result, next)
と書けば、仮に前の関数が何も渡してなければresultにundefinedを入れ、nextに関数が入る。
var async = require('neo-async'); async.angelFall([ function(next) { var foo = false; anAsyncMethod(foo, next); }, function(result, next) { // 定義してる引数の個数が見られ、 // 2つなので、1つめに前の関数から渡される結果、2つめにnextが入る。 next(); } ], function(err) { });
前回の記事で紹介したFunction.length
を利用して実現されている。
注意点
async.angelFall([ function(next) { var foo = false; anAsyncMethod(foo, next); }, function(result) { // nextを呼ばないから仮引数としても定義したくないよ、というとき。(実際あり得る) // 仮引数を1つしか定義していないので、ここにnextと呼ぶべき関数が入ってしまい、 // 期待する値(result)が入ってこない。
他の解決案
waterfallのタスク関数はfunction(foo, next)
ではなく、function(next, foo)
の順で取るように仕様変更するという話。
これをすれば、fooがこようがこまいが、nextの位置はずれないので上のような問題は起きない。
実際自分もこれは1年半前くらいに思ったのだけれど、Node.jsの「コールバック関数は最後にとる」という規約に反するので少し気持ち悪い。でも黒魔術しないですむし、async.waterfallに限って言えばこれもありだなぁと思う。
まとめ
Node.jsがcallbackを最後にとるというルールが、思わぬ形で影響を及ぼしてしまっている話でした。