Help us understand the problem. What is going on with this article?

Promiseの関数イディオム

More than 5 years have passed since last update.

環境: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を使えば...

ytkt
RailsやAWS Lambdaなどを使ってAPIやWebフロントエンドを書いていたり、Product Managementやったり。趣味ではホームオートメーションに力を入れたかったり。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away