Promise.all()で複数サイトのRSSを取得する非同期処理を並列に実行する

May 13, 2018

ES2015のPromise/Promise.all()を使用して、複数サイトのRSSを取得してみます。今回はNode.jsのExpressを使用します。

Expressとは

Node.jsのMVCフレームワークです。今回はMVCについての解説はせず、あくまで簡易的なウェブサーバとして使用します。

Express-generatorでプログラムの雛形を作る

まずはexpress-generatorをインストールし、Expressを使用したプログラムの雛形を作ります。
npmでexpress-generatorをグローバルインストールします。

npm install express-generator -g

続いて、プログラムの雛形を作成します。今回作成するプログラムではテンプレートエンジン(表示を制御するための機能)は使用しないので、テンプレートエンジンは何を選択しても良いです。今回はejsを選択します。

express --view=ejs myapp

依存関係のモジュールをインストールします。

cd myapp
npm install

expressを起動します。

DEBUG=myapp:* npm start

ブラウザからhttp://localhost:3000/にアクセスし、「Welcome to Express」と表示されることを確認します。

表示が確認できたらCtrl + cでExpressを停止させましょう。

pm2でExpressをデーモン化する

pm2を使ってExpressをデーモン化(常駐ソフト化)しましょう。デーモン化するメリットはたくさんありますが、最も恩恵を感じるのはコードを修正した際にpm2がファイルの変更を検知して自動でExpressを再起動してくれることです。

npmからpm2をグローバルインストールします。

npm install pm2 -g

pm2でexpressを起動します。--watchを付けることでファイルの変更を監視し、変更があった場合はpm2が自動でexpressを再起動してくれるようになります。

pm2 start bin/www --watch

ログを表示してページの状態が分かるようにします。

pm2 log

PromiseでRSSを取得してみる

RSSの取得とパースをするためにrss-parserという便利モジュールがあるのでインストールしておきます。

npm install rss-parser --save

routes/index.jsにコードを記述します。これはreddit.comとwired.comのRSSの記事タイトルと記事URLを取得して画面に出力します。

var express = require('express');
var router = express.Router();

//モジュールの読み込み
var rssParser = require('rss-parser');

/* GET home page. */
router.get('/', function(req, res, next) {

  //reddit.comとwired.comのRSS
  var urls = [
    "https://www.reddit.com/.rss",
    "https://www.wired.com/feed/rss"
  ];

  Promise.all(urls.map(function(url) {
    return new Promise(function(resolve, reject){
      var parser = new rssParser();
      var feed = parser.parseURL(url);
      //処理の終わり(解決)を通知する
      resolve(feed);
    });
  })).then(function(feeds) {
  
    //取得したRSSデータの整形
    var result = "";
    feeds.forEach(function(feed) {
       feed.items.forEach(function(item) {
         result += item.title + ':' + item.link + ' ';
       });
    });
    
    //画面に出力する
    res.send(result);
  });

});

module.exports = router;

実行する

http://localhost:3000/ にアクセスし、RSSが取得できることを確認してください。

コードの解説

以下のコードはExpress独自の書き方です。ルート(http://localhost:3000/ )にアクセスしたときにブロック内の処理を実行するようにしています。

router.get('/', function(req, res, next) {
    //ここに処理を書く
});

処理のおおまかな流れを日本語で書いてみます。

  1. ウェブサイトのRSSを取得するためのURLを用意し、urls配列に格納します。
  2. urls配列に格納したURLをmap関数で内部関数に渡します。
  3. 内部関数でPromiseインスタンスを用意します。Promiseインスタンスには引数としてresolve()reject()を受け取る関数(function(resolve, reject){...})を与えます。与えた関数の中で処理の成否を決めるためにresolve()(解決)とreject()(拒否)を呼び出します。resolve()reject()には引数として、その後の処理に引き継ぎたい値を与えることができます。今回はfeed(サイトから取得したRSS)を与えています。今回は「解決」できる前提で書いているのでreject()は使用していません。
  4. returnされたPromiseインスタンスはmap関数によって、配列にまとめられ、Promise.all()に渡されます。
  5. Promise.all()に渡された配列の中の全てのPromiseインスタンスがresolve()を実行すると.then(function(feeds){...});が実行されます。引数feedsにはresolve(feed)で渡されたfeedの配列が渡されます。
  6. RSSのタイトルとリンクを整形して画面に出力します。

このコードを理解するためのポイントは以下の3点です。

  • 全てのサイトのRSSの取得が完了した段階で画面に出力しなければいけないので非同期処理を書く必要があります。そのため、Promise.all()を使用します。
  • Promise.all()に与える引数はPromiseインスタンスを格納した配列でなければなりません。従って、urls配列にmap関数を使用して、内部関数に対象サイトのURLを渡します。そして、urls配列の要素数分のPromiseインスタンスを配列にまとめてからPromise.all()に返します。
  • Promise.all()は配列として返された全てのPromiseインスタンスがresolve()を実行したタイミングで同期が完了したとみなし、.then(function(){...});を実行します。

Javascriptに慣れていない初心者でも読みやすいように、今回はアロー関数は使用せずに書きました。余力があれば、アロー関数で書き直してみてください。

参考
Express - Node.js Web アプリケーション・フレームワーク
Express のアプリケーション生成プログラム

SmokyDog

I love dog and web.

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.