LoginSignup
17
18

More than 5 years have passed since last update.

コールバックベースの関数をプロミスオブジェクトに変換する

Posted at

参考書籍

記事の背景

プレーンなJavaScriptだけだと、ちょっと非同期処理が連続するプログロムを書こうとした時にコードの可読性が著しく落ちてしまうことがある。
そこで、外部ライブラリbluebirdを使うことでこの問題を改善しよう、ということで、Promiseを試してみた。

なお挙動理解のため、本来はすでにライブラリに実装してあるpromisifyを自作している (utilities.js)。

ケースとしては単純で、任意のURLのコンテンツをダウンロードし、所定のディレクトリ(なければ作成)にセーブする、という一連のタスクが、Promiseを使う対象になる。

コード

utilities.js
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);
        });
    };
};
download.js
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 のコールバック関数定義の中に、resolverejectを包んで、apply()していることになる。
この関数は、function (err, result) {}のようなコールバック関数をとる非同期処理全般に利用することができる。

[].slice.call(arguments);

この式単体で出てくるとなんのことかさっぱりわからなかったが、これについてとてもわかりやすい記事があった。
http://lealog.hateblo.jp/entry/2014/02/07/012014

17
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
18