[node.js]asyncを使って非同期処理を制御2 | 2年目エンジニアの戯言

2年目エンジニアの戯言

暇な時に更新。
すごく当たり前なことも多々書いてますが気にせずに。

     

asyncを使って非同期処理を制御2

前回の続きです。前回は非同期のtaskを順次または平行に処理を行う方法を書きましたが、今回は配列に特化した関数を紹介しようと思います。前回の記事の内容は理解していることを前提に書きます。

基礎編 配列の順次処理と並行処理

今回はasync.each, async.eachLimit, async.eachSeries, async.map, async.mapLimit, async.mapSeriesの6つを説明したいと思います。6つもありますが関数名で察しがつく通り、覚えることは3つぐらいです。eachとmapの違いはArray.forEachArray.mapかぐらいの違いです。~Seriesがあると無いとの違いは順次処理か並行処理かの違いです。~Limitもそこまで難しいことはないです。

async.each(arr, iterator, callback)

第一引数に配列を渡し、第二引数にはiteratorを渡します。説明が下手なので例を見せます。

var async = require('async');


var list = [
    { id: 'first', delay: 200 },
    { id: 'two', delay: 100 },
    { id: 'three', delay: 150 },
];
async.each(list, function(data, next) {
    setTimeout(function() {
        console.log(data.id, 'done!!');
        next();
    }, data.delay);
}, function complete(err) {
    console.log('all done!');
});


// --- output ---
// two done!!
// three done!!
// first done!!
// all done!

説明のために無名関数にcompleteと名づけたり、setTimeoutの時間をずらしています。

async.eachasync.parallelのように並行処理です。Array.forEachのようにiteratorの処理を実行します。iteratorの第二引数のnext関数を呼ぶことで処理の完了を知らせます。配列の中身すべてのnextが呼ばれるとcompleteを実行します。説明下手ですがasync.parallelを理解してればわかると思います。実行結果はdelayの順番で表示されましたね。ちなみにasync.forEachというaliasが用意されているのでお好きな方でお使いください。

async.eachLimit(arr, limit, iterator, callback)

引数にlimitが増えました。実行結果を見てみましょう。

var async = require('async');


var list = [
    { id: 'first', delay: 200 },
    { id: 'two', delay: 100 },
    { id: 'three', delay: 150 },
];
async.eachLimit(list, 2, function(data, next) {
    setTimeout(function() {
        console.log(data.id, 'done!!');
        next(null, data.id);
    }, data.delay);
}, function(err) {
    console.log('all done!');
});


// --- output ---
// two done!!
// first done!!
// three done!!
// all done!

実行結果が少し変わりましたね。基本的な処理の流れはasync.eachと同じです。ただし、async.eachはすべての配列データを並行処理していましたが、async.eachLimitlimit分の処理しか同時に実行しません。今回の例でいうと2個同時にしか並行処理しないのでfirstとtwoが実行されて100ms後にtwoが終わったタイミングでthreeが開始します。threeが始まったことにはfirstのdelayは残り100msですのでthreeより先にfirstが出力されたのです。順序を気にせず並行処理をしてもよいが同時に走り過ぎると重たい非同期処理等があったときによく使われる関数です。aliasにasync.forEachLimitがあります。

async.eachSeries(arr, iterator, callback)

async.eachの順次処理版です。async.seriesのように配列データを順次iteratorで実行していきます。

var async = require('async');


var list = [
    { id: 'first', delay: 200 },
    { id: 'two', delay: 100 },
    { id: 'three', delay: 150 },
];
async.eachSeries(list, function(data, next) {
    setTimeout(function() {
        console.log(data.id, 'done!!');
        next();
    }, data.delay);
}, function(err) {
    console.log('all done!');
});


// --- output ---
// first done!!
// two done!!
// three done!!
// all done!

実行結果を見ての通りです。僕は割と使います。特に順序が重要じゃない処理もなんとなくasync.eachSeries使います。なんとなくasync.eachLimitは使いません。多分世間的にはasync.eachLimitの方が利便性ありそうですがw aliasにasync.forEachSeriesがあります。

async.map(arr, iterator, callback)

async.eachArray.forEachとなればasync.mapArray.mapです。callback関数の第二引数に値が返ってきます。

var async = require('async');


var list = [
    { id: 'first', delay: 200 },
    { id: 'two', delay: 100 },
    { id: 'three', delay: 150 },
];
async.map(list, function(data, next) {
    setTimeout(function() {
        console.log(data.id, 'done!!');
        next(null, data.id);
    }, data.delay);
}, function(err, results) {
    console.log(JSON.stringify(results));
});


// --- output ---
// two done!!
// three done!!
// first done!!
// ["first","two","three"]

処理の流れはasync.eachと同じです。resultsにデータが返ってきますが実行結果順ではなくてlistと同じ添字で入っていきます。この辺はasync.parallel等と同じです。

async.mapLimit(arr, limit, iterator, callback)

もう説明は特に必要ないと思いますが例だけ載せておきます。

var async = require('async');


var list = [
    { id: 'first', delay: 200 },
    { id: 'two', delay: 100 },
    { id: 'three', delay: 150 },
];
async.mapLimit(list, 2, function(data, next) {
    setTimeout(function() {
        console.log(data.id, 'done!!');
        next(null, data.id);
    }, data.delay);
}, function(err, results) {
    console.log(JSON.stringify(results));
});


// --- output ---
// two done!!
// first done!!
// three done!!
// ["first","two","three"]

async.mapSeries(arr, iterator, callback)

同様にasync.eachSeriesのmap版です。

var async = require('async');


var list = [
    { id: 'first', delay: 200 },
    { id: 'two', delay: 100 },
    { id: 'three', delay: 150 },
];
async.mapSeries(list, function(data, next) {
    setTimeout(function() {
        console.log(data.id, 'done!!');
        next(null, data.id);
    }, data.delay);
}, function(err, results) {
    console.log(JSON.stringify(results));
});


// --- output ---
// first done!!
// two done!!
// three done!!
// ["first","two","three"]

asyncの基礎となる関数の説明は以上です。年末に書いたのに上げるのがだいぶ遅くなった(汗