10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Promiseの関数イディオム

Last updated at Posted at 2015-12-05

環境:v0.10.41

非同期タスクを動的に生成し、Waterfall実行する

Promiseを使ってWaterfallを実現したい時、下記のようにメソッドチェーンで記述することが出来る。

taskA.then(taskB)
    .then(function(){
        console.log('DONE');
    });

しかし、「特定URLのパラメータを20ずつインクリメントしながら5回順次叩く」というような多数の非同期タスクを順次実行したい場合、実行したいタスク個分のthen()を書かなくてはならず、下記のように記述がとても煩雑になってしまう。(fetch()は1秒後にresolve()をコールする。)

var Promise = require('promise');

function fetch(param) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log(param);
            resolve();
        }, 1000);
    });
}

(function main() {
    var tasks = [0,1,2,3,4].map(function(index) {
        return function() {
            return fetch(index);
        };
    });

    tasks[0]()
        .then(tasks[1])
        .then(tasks[2])
        .then(tasks[3])
        .then(tasks[4])
        .then(function() {
            console.log('DONE!');
        });
})();

そこでPromiseをシンプルにWaterfallで実行する関数イディオム(promiseWaterfall())が下のコード。
promiseWaterfall()はPromiseオブジェクトを返す関数の配列を引数に取り、メソッドチェーンを生成し、最後に実行される関数のPromiseオブジェクトを返す。

var Promise = require('promise');

function promiseWaterfall(tasks) {
    var finalTaskPromise = tasks.reduce(function(prevTaskPromise, task) {
        return prevTaskPromise.then(task);
    }, Promise.resolve());                                                                                                                                                        

    return finalTaskPromise;
}

function fetch(param) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log(param);
            resolve();
        }, 1000);
    });
}

(function main() {
    var tasks = [0,1,2,3,4].map(function(index) {
        return function() {
            return fetch(index);
        };
    });

    promiseWaterfall(tasks).then(function() {
        console.log('DONE!');
    });
})();

これくらいの内容だと記述量が逆に増えてしまうが、処理が多数ある場合にとても効果を発揮する。

と思ったらv0.11以降(v0.11は--harmonyオプションが必要)はyieldco というモジュールの組み合わせでより直感的に書けるらしい。
Node.js - 最近のjs非同期処理 PromiseとGeneratorの共存 - Qiita
v0.10の世界で生きていた。。。辛い。

非同期処理を不定回数実行する

次は一定回数の実行ではなく、不定回数の非同期処理を繰り返す処理をPromiseで書く。
下のfetchItems()は引数で与えられた個数分itemを取得して返す。
fetch()は要素数3の配列を返すように上述のコードを書き換えたもの。

var Promise = require('promise');

function fetch(param) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            var items = [param, param, param];
            console.log('fetch() returns:', items);
            resolve(items);
        }, 1000);
    });
}

function fetchItems(count) {
    var items = [];

    return new Promise(function(resolve) {
        var recursiveFetch = function(index) {
            fetch(index).then(function (result) {
                items = items.concat(result);

                if (items.length > count) {
                    resolve(items.slice(0, count));
                } else {
                    recursiveFetch(++index);
                }
            });
        };

        recursiveFetch(0);
    });
}

(function main() {
    fetchItems(10).then(function(result) {
        console.log('fetchItems() returns:', result);
    });
})(); 

実行結果は下のようにログが1秒おきに1行ずつ表示される。

$ node index.js 
fetch() returns: [ 0, 0, 0 ]
fetch() returns: [ 1, 1, 1 ]
fetch() returns: [ 2, 2, 2 ]
fetch() returns: [ 3, 3, 3 ]
fetchItems() returns: [ 0, 0, 0, 1, 1, 1, 2, 2, 2, 3 ]

これもたぶんyeildcoを使えば...

10
10
1

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
10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?