参考書籍
記事の背景
プレーンなJavaScriptだけだと、ちょっと非同期処理が連続するプログロムを書こうとした時にコードの可読性が著しく落ちてしまうことがある。
そこで、外部ライブラリbluebird
を使うことでこの問題を改善しよう、ということで、Promise
を試してみた。
なお挙動理解のため、本来はすでにライブラリに実装してあるpromisify
を自作している (utilities.js
)。
ケースとしては単純で、任意のURLのコンテンツをダウンロードし、所定のディレクトリ(なければ作成)にセーブする、という一連のタスクが、Promiseを使う対象になる。
コード
var Promise = require('bluebird');
module.exports.promisify = function(callbackBasedApi) {
return function promisified() {
// promisified() に渡る引数を配列に変換する
var args = [].slice.call(arguments);
return new Promise(function(resolve, reject) {
args.push(function(err, result) {
if (err) {
return reject(err);
}
// このarguments には、err と result(単体のオブジェクト、または配列)が入っている
if (arguments.length <= 2) {
resolve(result);
} else {
// arguments の index の1以降を配列として resolve の引数に渡す
resolve([].slice.call(arguments, 1));
}
});
// 上で詰めたコールバック関数も含めて、引数にAPIを適用する
callbackBasedApi.apply(null, args);
});
};
};
var path = require('path');
var Promise = require('bluebird');
var utilities = require('./utilities');
// callback-based-api を promisify する!
var request = utilities.promisify(require('request'));
var mkdirp = utilities.promisify(require('mkdirp'));
var fs = require('fs');
var readFile = utilities.promisify(fs.readFile);
var writeFile = utilities.promisify(fs.writeFile);
// データをダウンロードして
// 保存ディレクトリを作成し
// データを書き込み
// 後処理する
// というタスクを逐次的に行っている。
function download(url, filename) {
console.log('Downloading ' + url);
var body;
return request(url)
.then(function(results) {
body = results[1];
return mkdirp(path.dirname(filename));
})
.then(function() {
return writeFile(filename, body);
})
.then(function() {
console.log('Downloaded and saved: ' + url);
return body;
});
}
download('http://qiita.com', './sample/index.html');
##実行結果
このプログラムを実行したディレクトリ配下に、sample
ディレクトリが作成され、index.htmlファイルが格納される。
##終わりに
コードを眺めるとややこしい、またはよくわからないものがあったので、個別に調べ、補足を入れることにした。
###new Promise(function(resolve, reject) {});
これは、 引数に取る関数に基づいて、 fullfil または reject する promise オブジェクトを作るコンストラクタで、
あとで呼び出されるthen() 関数の引数として渡されるコールバック関数がこのコンストラクタの実引数となる( と思う)。
この関数の文脈では、callback-based-api のコールバック関数定義の中に、resolve
とreject
を包んで、apply()
していることになる。
この関数は、function (err, result) {}
のようなコールバック関数をとる非同期処理全般に利用することができる。
[].slice.call(arguments);
この式単体で出てくるとなんのことかさっぱりわからなかったが、これについてとてもわかりやすい記事があった。
http://lealog.hateblo.jp/entry/2014/02/07/012014