情報アイランド

「情報を制する者は世界を制す」をモットーに様々な情報を提供することを目指すブログです。現在はプログラミング関連情報が多めですが、投資関連情報も取り扱っていきたいです。

Node.jsで複数のプロミスを1つに纏める

2016/09/02

全てのプロミスの完了の待機

複数のプロミスの全てが保留状態から完了状態に変わった後に保留状態から完了状態に変わる新しいプロミスを作成するにはプロミスクラスのall関数やhash関数やprops関数を使用します。

all関数は全てのプロミスクラスで提供されています(ただし、whenモジュールの場合、when.all関数となります)。

var promise = require('promise');

var p = promise.all([p1, p2, 'xxx']);

第1引数に複数のプロミスを配列として指定します。あるいは、配列以外のイテラブルとして指定することもできます。

あるいは、プロミスを指定する代わりに任意の値を指定することもできます。この場合、プロミスクラスのresolve関数によりこの値を結果とする完了状態のプロミスが作成されます。

返り値として新しいプロミスが得られます。

このプロミスの結果は全てのプロミスの結果を順番に配列に格納したものになります。

なお、何れか1つのプロミスが保留状態からエラー状態に変わった場合にはこのプロミスも保留状態からエラー状態に変わります

hash関数は下のプロミスクラスで提供されています。

  • rsvp.Promiseクラス

props関数は下のプロミスクラスで提供されています。

  • bluebirdクラス

hash関数やprops関数はall関数とほぼ同じですが、第1引数の複数のプロミスをオブジェクトとして指定します。また、返り値として得られるプロミスの結果もオブジェクトとなります。

var rsvp = require('rsvp');

var p = rsvp.Promise.hash({
    xxx: p1, 
    yyy: p2, 
    zzz: 'zzz'
});

全てのプロミスの完了又はエラー発生の待機

複数のプロミスの全てが保留状態から完了状態やエラー状態に変わった後に保留状態から完了状態に変わる新しいプロミスを作成するにはプロミスクラスのallSettled関数やsettle関数やhashSettled関数を使用します。

allSettled関数は下のプロミスクラスで提供されています。

  • rsvp.Promiseクラス
  • q.Promiseクラス

settle関数は下のプロミスクラスで提供されています(ただし、whenモジュールの場合、when.settle関数となります)。

  • when.promiseクラス
var p = rsvp.Promise.allSettled([p1, p2, 'xxx']);

第1引数に複数のプロミスを配列として指定します。あるいは、配列以外のイテラブルとして指定することもできます。

あるいは、プロミスを指定する代わりに任意の値を指定することもできます。この場合、プロミスクラスのresolve関数によりこの値を結果とする完了状態のプロミスが作成されます。

返り値として新しいプロミスが得られます。

このプロミスの結果はrsvp.Promiseクラスの場合、全てのプロミスの結果やエラーオブジェクトを順番に配列に格納したものになり、q.Promiseクラスやwhen.promiseクラスの場合、全てのプロミスの状態を表すオブジェクトを順番に配列に格納したものになります。

プロミスの状態を表すオブジェクトは下のようなプロパティを有します。

  • state・・・プロミスが完了状態であるかエラー状態であるかを表します。完了状態である場合にはfulfilledとなり、エラー状態である場合にはrejectedとなります。
  • value・・・結果です。
  • reason・・・エラーオブジェクトです。

hashSettled関数は下のプロミスクラスで提供されています。

  • rsvp.Promiseクラス

hashSettled関数はallSettled関数とほぼ同じですが、第1引数の複数のプロミスをオブジェクトとして指定します。また、返り値として得られるプロミスの結果もオブジェクトとなります。

var p = rsvp.Promise.hashSettled({
    xxx: p1, 
    yyy: p2, 
    zzz: 'zzz'
});

サンプルコード1

promise-all-1.js

var promise = require('promise');
var util = require('util');

var pres1 = new promise(function (resolve, reject) {
    setTimeout(function () {
        util.log('promise resolve 1.');
        resolve('pres1');
    }, 1000);
});
var pres2 = new promise(function (resolve, reject) {
    setTimeout(function () {
        util.log('promise resolve 2.');
        resolve('pres2');
    }, 2000);
});
var pres3 = new promise(function (resolve, reject) {
    setTimeout(function () {
        util.log('promise resolve 3.');
        resolve('pres3');
    }, 3000);
});
var pres4 = new promise(function (resolve, reject) {
    setTimeout(function () {
        util.log('promise resolve 4.');
        resolve('pres4');
    }, 4000);
});

var prej = new promise(function (resolve, reject) {
    setTimeout(function () {
        util.log('promise reject.');
        reject(new Error('prej'));
    }, 5000);
});

var ps1 = promise.all([pres1, pres2, pres3, pres4, prej]);
var ps2 = promise.all([pres1, pres2, pres3, pres4]);

ps1.done(function (result) {
    util.log(result);
}, function (err) {
    console.error(err);
});

ps2.done(function (result) {
    util.log(result);
}, function (err) {
    console.error(err);
});

util.log('program started.');

使用パッケージ

  • promise
    npm install promiseでインストールします。

実行結果

C:\work\node>node promise-all-1.js
9 Jul 15:46:23 - program started.
9 Jul 15:46:24 - promise resolve 1.
9 Jul 15:46:25 - promise resolve 2.
9 Jul 15:46:26 - promise resolve 3.
9 Jul 15:46:27 - promise resolve 4.
9 Jul 15:46:27 - [ 'pres1', 'pres2', 'pres3', 'pres4' ]
9 Jul 15:46:28 - promise reject.
Error: prej
    at Timeout._onTimeout (C:\work\node\promise-all-1.js:32:10)
    at tryOnTimeout (timers.js:224:11)
    at Timer.listOnTimeout (timers.js:198:5)

サンプルコード2

別の記事で作成したfs-readdir-ext.js(→フォルダの直下にある特定の拡張子のファイルのリストを取得する)をプロミスを使用するように書き換えたものです。

promise-all-2.js

var promise = require('promise');
var fs = require('fs');
var path = require('path');

if (process.argv.length < 4) {
    console.error('lack argument.');
    process.exit(1);
}

var p1 = new promise(function (resolve, reject) {
    fs.readdir(process.argv[2], function (err, list) {
        if (err) {
            reject(err);
        }
        else {
            resolve(list);
        }
    });
});

var p2 = p1.then(function (list) {
    var ps = [];
    for (var i = 0; i < list.length; i++) {
        let e = list[i];
        ps.push(new promise(function (resolve, reject) {
            fs.stat(path.join(process.argv[2], e), function (err, stats) {
                if (err) {
                    reject(err);
                }
                else if (stats.isFile() && path.extname(e) === process.argv[3]) {
                    resolve(e);
                }
                else {
                    resolve(null);
                }
            });
        }));
    }
    return promise.all(ps);
});

p2.done(function (flist) {
    for (var i = 0; i < flist.length; i++) {
        if (flist[i]) {
            console.log(flist[i]);
        }
    }
}, function (err) {
    console.error(err);
    process.exit(1);
});

使用パッケージ

  • promise
    npm install promiseでインストールします。

実行結果

ファイルを示すパスや存在しないパスC:\worksを指定した場合にはエラーとなっていることが分かります。

C:\work\node>node promise-all-2.js C:\work\node .js
error-stack-trace-limit-3.js
fs-access-sync.js
fs-access.js
fs-rename-sync.js
fs-rename.js
http-server.js
kramed.js
markdown-attribute.js
promise-all-1.js
promise-all-2.js
promise-done-1.js
promise-done-2.js
promise-nodeify.js
promise-resolve-reject.js
promise-then-1.js
unfluff.js

C:\work\node>node promise-all-2.js promise-all-2.js .js
{ Error: ENOTDIR: not a directory, scandir 'C:\work\node\promise-all-2.js'
    at Error (native)
  errno: -4052,
  code: 'ENOTDIR',
  syscall: 'scandir',
  path: 'C:\\work\\node\\promise-all-2.js' }

C:\work\node>node promise-all-2.js C:\works .js
{ Error: ENOENT: no such file or directory, scandir 'C:\works'
    at Error (native)
  errno: -4058,
  code: 'ENOENT',
  syscall: 'scandir',
  path: 'C:\\works' }

関連

pizyumi
プログラミング歴19年のベテランプログラマー。業務システム全般何でも作れます。現在はWeb系の技術を勉強中。
スポンサーリンク

-Node.js