アダプタ的な関数で非同期コールバックをつなぐ
昨日のエントリでは、「2つのファイルを同時に読み、結果を連結して書き出し、書き出した内容をもう一度読む」という処理を以下のコードで実現しました。
var gate = require('gate'); var fs = require('fs'); var latch = gate.latch(); fs.readFile('path1', 'utf8', latch(1)); fs.readFile('path2', 'utf8', latch(1)); latch.await(function (err, results) { if (err) throw err; fs.writeFile('path3', results[0] + results[1], function (err) { if (err) throw err; fs.readFile('path3', 'utf8', function (err, data) { if (err) throw err; console.log(data); console.log('all done'); }); }); });
このコード、1つ気になるところがあるんですよね。それはエラー処理。「if (err) throw err」の処理が散らばっています。コードを読みにくくする上に、一律でエラーハンドリングの方法を変更できないのが欠点です。
そこで手軽に問題を解決する方法を考えました。ずばり、アダプタ的な関数を間にはさめばいいんです。ここではbindと呼びます(F#のワークフローにならって)。このbindはFunction.prototype.bindとは別物です。次のように実装します。
function bind(callback) { return function (err) { if (err) { err.message += ', callbackLocation: ' + err.callbackLocation; throw err; } callback.apply(null, Array.prototype.slice.call(arguments, 1)); } }
err.callbackLocationは、gate 0.1.0 で導入したスタック情報です。これがあると並列処理のどれがエラーと関連しているかわかります。
bindを使ってこう書けるようになりました。
var gate = require('gate'); var fs = require('fs'); var latch = gate.latch(); fs.readFile('path1', 'utf8', latch(1)); fs.readFile('path2', 'utf8', latch(1)); latch.await(bind(function (results) { fs.writeFile('path3', results[0] + results[1], bind(function () { fs.readFile('path3', 'utf8', bind(function (data) { console.log(data); console.log('all done'); })); })); }));
どうでしょう、見た目にちょっとすっきり。
bind的な関数は、非同期コールバックを使うときは常に使っておいたほうが便利じゃないかなーと思います、gateを使う使わないに関わらず。ログ出力、エラーハンドリングなどを簡単に一律制御できるので便利です。