request-promiseで同期的に値を取得する

requestで値を返したい

例えば、requestでGoogleのページからtitleを取得する際、requestで取得したtitleを別の処理でも使いたいと思い、次のように書いたとします。

// titleFetch.js
const request = require('request');

let title;
request('https://www.google.com/', (error, response, body) => {
    if (!error && response.statusCode === 200) {
        title = body.match(/<title>.+<\/title>/g)[0]
            .replace(/<\/?title>/g, "")
        console.log(`inside : ${title}`);
    } else {
        throw error;
    }
})
console.log(`outside: ${title}`);

requestは非同期処理なので、結果は次のようになります。

$ node titleFetch.js
outside: undefined
inside : Google

同期処理である外側のconsole.logが最初に実行されるためtitleの中身はundefinedとなります。

これの解決法としてはcallback関数を使う方法もありますが、ES6から追加されたPromiseオブジェクトを利用します。

request-promiseを使う

Promiseをサポートしたrequest-promiseというモジュールを使います。requestでpromiseオブジェクトが返ってくるので.thenでメソッドチェーンを作ることで同期的に処理を進めることができます。

request-promiseを使って上記のコードを書き換えてみます。

// titleFetch.js
const request = require('request-promise');

request('https://www.google.com/')
    .then(body => {
        const title = body.match(/<title>.+<\/title>/g)[0]
            .replace(/<\/?title>/g, "")
        console.log(`1: ${title}`);
        return title;
    })
    .then(title => {
        console.log(`2: ${title}`);
    })
    .catch(error => {
        throw error;
    })

これを実行すると次のようになります。

$ node titleFetch.js
1: Google
2: Google

うまく動きました。上記の例だとわざわざメソッドチェーンを使うまでもない感じもしますが、非同期処理をメソッドに持つクラスを使いたい場面なんかだと有効に使えるのではないかと思います。(初心者の手探りなので見当違いな方法かもしれませんが…。)

各国のGoogleのドメインを指定して任意の検索ワードの検索順位一位のtitleを取得するものを書いてみました。(と言っても日本からのアクセスなので日本語の検索結果となります。)クラスでドメインごとのインスタンスが作成できるのと、Promiseチェーンの中で別の検索結果を取得してそれらを合わせて処理するなど柔軟に使いまわせそうです。

// googleFetch.js
const request = require('request-promise');

class Google {
  constructor(domain) {
    this.domain = domain;
  }
  fetch(keyword) {
    const options = {
      uri: `https://www.google.${this.domain}/search?q=${keyword}`,
      timeout: 10000
    }
    return request(options)
  }
}

const googlecom = new Google('com'); // インスタンスを作成

googlecom.fetch('request') // 'request' で検索
  .then(async body => {
    let resultOfRequest = body;
    let resultOfRequestPromise = await googlecom.fetch('request-promise') // 'request-promise' で検索
    return [resultOfRequest, resultOfRequestPromise];
  })
  .then(results => {
    resultOfRequest = results[0].match(/<h3 class="r">.+?<\/h3>/g)[0]
      .replace(/<.*?>/g, "");
    resultOfRequestPromise = results[1].match(/<h3 class="r">.+?<\/h3>/g)[0]
      .replace(/<.*?>/g, "");
    console.log(`requstの検索一位: ${resultOfRequest}`)
    console.log(`requst-promiseの検索一位: ${resultOfRequestPromise}`)
  })
  .catch(error => {
      throw error;
  })

メソッドではreqeust-promise関数ごと返します。また、Promiseチェーンの中で二度目の検索を行う場合はasync/awaitで次の処理に渡すまでに待たせることで値を渡しています。(これをやらないと非同期処理なのでやはりundefinedになります。)これを実行すると以下のような結果になります。

$ node googleFetch.js
requstの検索一位: requestの意味・使い方 - 英和辞典 Weblio辞書
requst-promiseの検索一位: GitHub - request/request-promise: The simplified HTTP request ...

日本語は特殊文字で表示されてましたが、変換した結果を表示しています。これでPromiseを返すメソッドができ、複数の非同期処理を合わせた処理も可能になりました。非同期処理はよく分かってないのでクラスのメソッドを読んでthenチェーンを繋いでいく書き方が良いのかはよく分かってないのですが、とりあえず動いたので今は良しとします。