自分用の co-foreach を作った話

最近nodejs触り始めた,楽しい.

co-foreachもうあるけど,自分好みじゃないんで自分用のやつ作った話.

coの中で,forEachしてその中でyieldしたいとする.

'use strict';
var co = require('co');
var request = require('co-request');

// サイトのタイトルを取ってくる
co(function*(){
  var urlList = [
    'http://example.com',
    'http://google.com',
  ];

  urlList.forEach(function*(url) {
    var res = yield request.get(url);
    var match = res.body.match(/<title>([\s\S]*?)<\/title>/);
    if (match) console.log(match[1].trim());
  });

  process.exit();
});

これはうまくいかない.何も表示されない.

そこで,co-foreachが既にあるので使ってみる.

'use strict';
var co = require('co');
var request = require('co-request');

var foreach = require('co-foreach');

co(function*(){
  var urlList = [
    'http://example.com',
    'http://google.com',
  ];

  yield foreach(urlList, function*(url) {
    var res = yield request.get(url);
    var match = res.body.match(/<title>([\s\S]*?)<\/title>/);
    if (match) console.log(match[1].trim());
  });

  process.exit();
});

結果は次のようになる.

Google
Example Domain

お分かりいただけるだろうか,順序が逆である.

これは,配列の順序が逆になっているわけではない.レスポンスが早い順のようである.

試しに次のコードを走らせてみるといい.

'use strict';
var co = require('co');
var request = require('co-request');
var wait = require('co-wait');

var foreach = require('co-foreach');

co(function*(){
  var urlList = [
    'http://example.com',
    'http://google.com',
  ];

  yield foreach(urlList, function*(url) {
    // Googleにアクセスする前に1秒待つ
    if (url.match(/google/)) yield wait(1000);
    var res = yield request.get(url);
    var match = res.body.match(/<title>([\s\S]*?)<\/title>/);
    if (match) console.log(match[1].trim());
  });

  process.exit();
});

すると結果はこうなる.

Example Domain
Google

つまり,並列処理(っぽく)なっているのだ.

これは困る.とても困る.誰が並列処理しろといった.


さて,今回ぼくが欲しかったのはcoのyieldが効いていて,逐次処理するforEachなわけだ.

そこでぼくは僕のためにco-foreachを作った.

それを使うとこうなる.

'use strict';
var co = require('co');
var request = require('co-request');

var foreach = require('co-foreach');

co(function*(){
  var urlList = [
    'http://example.com',
    'http://google.com',
  ];

  yield urlList.forEach(function*(url) {
    var res = yield request.get(url);
    var match = res.body.match(/<title>([\s\S]*?)<\/title>/);
    if (match) console.log(match[1].trim());
  });

  process.exit();
});

結果は以下の通り.

Example Domain
Google

ちゃんと順番通りである.よろしい.

もちろん,逐次処理なわけだから先程のコードより全体にかかる時間が長くなるが,ぼくがやりたいのは逐次処理なのでいいのだ.


ちなみにGithubにあげておいた.まだnpmには登録していない.(いい名前が思いつけば上げる)

インストールする時は,npm install 3846masa/co-foreachでできる.

追記

co-rega-foreachになった.


余談だが,これはArray.prototype.forEachを書き換えている.

もちろん,通常通りのforEachも使える.generator functionかそうでないかで挙動を分けている.

そこは心配しなくて大丈夫.

'use strict';
var foreach = require('co-foreach');

var urlList = [
  'http://example.com',
  'http://google.com',
];

urlList.forEach(function(url) {
  console.log(url);
});

process.exit();
http://example.com
http://google.com

for文と添字で回せばいいだろって?

Perlプログラマには、リストを反復処理するときに、C言語風のforループと添え字を使うのを避けようとする強い傾向がある。

覚えとくといい.