LoginSignup
1
2

More than 5 years have passed since last update.

Webサイトからテキストデータを取得するコードをプロミスを使って書き直してみた

Last updated at Posted at 2016-11-30

以前ブログに投稿したWebサイトからテキストデータを取得するコードをプロミスを使って書き直してみました。

プロミスに関する説明はブログに書いています。

下が書き直してみたコードです。説明はもっと下にあります。

var co = require('co');
var bluebird = require('bluebird');
var url = require('url');
var http = require('http');
var https = require('https');
var concatStream = require('concat-stream');
var pump = require('pump');
var mimelib = require('mimelib');
var jschardet = require('jschardet');
var iconvLite = require('iconv-lite');

function getUrl (option) {
    return co(function* () {
        option = combine(option, {});

        var res = (yield getRes(option)).res;
        var data = yield rstream2promise(res);
        if (res.statusCode === 200) {
            var encoding = undefined;
            if (res.headers['content-type'] !== undefined) {
                encoding = mimelib.parseHeaderLine(res.headers['content-type']).charset;
            }
            if (encoding === undefined) {
                encoding = jschardet.detect(data).encoding;
            }
            if (encoding !== undefined && iconvLite.encodingExists(encoding)) {
                option.data = iconvLite.decode(data, encoding);
                return option;
            }
            else {
                throw new Error('can\'t detect encoding.');
            }
        }
        else {
            throw new Error(res.statusMessage);
        }
    });
}

function getRes (option) {
    return new bluebird(function (resolve, reject) {
        option = combine(option, {
            url: 'https://www.google.co.jp/', 
            timeout: 10000
        });
        if (typeof option.url === 'string') {
            option.url = url.parse(option.url, true);
        }
        else if (typeof option.url === 'object') {
        }
        else {
            reject(new TypeError('unknown type.'));
        }
        if (option.url.protocol === 'http:') {
            option.module = http;
        }
        else if (option.url.protocol === 'https:') {
            option.module = https;
        }
        else {
            reject(new TypeError('unknown protocol.'));
        }

        var client = option.module.get(option.url, function (res) {
            option.res = res;
            resolve(option);
        });
        client.setTimeout(option.timeout, function () {
            client.abort();
            reject(new Error('timeouted.'));
        });
        client.on('error', function (err) {
            reject(err);
        });
    });
}

function rstream2promise (stream) {
    return new bluebird(function (resolve, reject) {
        pump(stream, concatStream(function (data) {
            resolve(data);
        }), function (err) {
            reject(err);
        });
    });
};

function combine (option, def) {
    if (option === undefined) {
        option = {};
    }
    for (key of Object.keys(def)) {
        if (option[key] === undefined) {
            option[key] = def[key];
        }
    }
    return option;
};

説明

function getUrl (option) {
    return co(function* () {
・・・
    });
}

getUrl関数が本体です。この関数は第1引数としてオプションを受け取り、Webサイトからテキストデータを取得するプロミスを返します。

このプロミスはcoモジュールのco関数を使用して作成しています。co関数に関しては下の記事に書きました。

getUrl関数の中身を見ていきます。

function getUrl (option) {
    return co(function* () {
        option = combine(option, {});
・・・
    });
}

function combine (option, def) {
    if (option === undefined) {
        option = {};
    }
    for (key of Object.keys(def)) {
        if (option[key] === undefined) {
            option[key] = def[key];
        }
    }
    return option;
};

combine関数は2つのオブジェクトを1つに纏めるだけの関数です。この関数で引数のオプションとデフォルトのオプションを結合しています。

function getUrl (option) {
    return co(function* () {
・・・
        var res = (yield getRes(option)).res;
・・・
}

次に、getRes関数を使用しています。この関数の第1引数にオプションを渡し、返り値のresプロパティを取得しています。

getRes関数を見ましょう。

function getRes (option) {
    return new bluebird(function (resolve, reject) {
・・・
    });
}

getRes関数はWebサイトにHTTPリクエストを送信し、HTTPレスポンスを取得する関数です。この関数は第1引数としてオプションを受け取り、WebサイトにHTTPリクエストを送信するプロミスを返します。

このプロミスはbluebirdモジュールのbluebird関数を使用して作成しています。bluebird関数はプロミスを作成するための関数です。詳細は上のプロミスに関する記事を参照してください。

function getRes (option) {
    return new bluebird(function (resolve, reject) {
        option = combine(option, {
            url: 'https://www.google.co.jp/', 
            timeout: 10000
        });
        if (typeof option.url === 'string') {
            option.url = url.parse(option.url, true);
        }
        else if (typeof option.url === 'object') {
        }
        else {
            reject(new TypeError('unknown type.'));
        }
        if (option.url.protocol === 'http:') {
            option.module = http;
        }
        else if (option.url.protocol === 'https:') {
            option.module = https;
        }
        else {
            reject(new TypeError('unknown protocol.'));
        }
・・・
    });
}

この関数では最初にオプションに対する処理を行っています。

combine関数を使用して引数のオプションとデフォルトのオプションを結合しています。

このオプションにはurlオプションとtimeoutオプションを指定することができます。

  • url・・・HTTPリクエストのURLです。デフォルトはhttps://www.google.co.jp/です。
  • timeout・・・HTTPリクエストの送信がタイムアウトする時間(ミリ秒単位)です。デフォルトは10000です。

urlオプションの値が文字列である場合にはurl.parse関数を使用してパースします。

パースした結果のprotocolプロパティがhttp:である場合にはHTTPリクエストを送信するのにhttpモジュールを使用することにします。https:である場合にはhttpsモジュールを使用することにします。

function getRes (option) {
    return new bluebird(function (resolve, reject) {
・・・
        var client = option.module.get(option.url, function (res) {
            option.res = res;
            resolve(option);
        });
        client.setTimeout(option.timeout, function () {
            client.abort();
            reject(new Error('timeouted.'));
        });
        client.on('error', function (err) {
            reject(err);
        });
    });
}

http.get関数かhttps.get関数を使用してURLに対するHTTPリクエストを送信します。

HTTPリクエストに対するHTTPレスポンスが返ってきた場合にはoption.resにHTTPレスポンスを表すオブジェクトを格納し、optionをプロミスの結果として返します。

また、http.get関数やhttps.get関数の返り値はHTTPクライアントを表すオブジェクトとなりますので、このオブジェクトのsetTimeout関数を使用してタイムアウト時間を設定します。

タイムアウトした場合にはエラーを発生させます。

getResの中身が分かったのでgetUrl関数に戻りましょう。

function getUrl (option) {
    return co(function* () {
・・・
        var res = (yield getRes(option)).res;
        var data = yield rstream2promise(res);
・・・
    });
}

function rstream2promise (stream) {
    return new bluebird(function (resolve, reject) {
        pump(stream, concatStream(function (data) {
            resolve(data);
        }), function (err) {
            reject(err);
        });
    });
};

getRes関数の返り値のresプロパティはHTTPレスポンスを表すオブジェクトでしたが、これはHTTPレスポンスボディを読み込む読み込みストリームでもあります。

ストリームに関する説明はブログに書いています。

このストリームをrstream2promise関数の第1引数に渡しています。この関数は読み込みストリームをプロミスに変換する関数です。この関数に関しては下の記事を参照してください。

この関数の返り値のプロミスの結果としてHTTPレスポンスボディの内容が得られます。

function getUrl (option) {
    return co(function* () {
・・・
        if (res.statusCode === 200) {
            var encoding = undefined;
            if (res.headers['content-type'] !== undefined) {
                encoding = mimelib.parseHeaderLine(res.headers['content-type']).charset;
            }
            if (encoding === undefined) {
                encoding = jschardet.detect(data).encoding;
            }
            if (encoding !== undefined && iconvLite.encodingExists(encoding)) {
                option.data = iconvLite.decode(data, encoding);
                return option;
            }
            else {
                throw new Error('can\'t detect encoding.');
            }
        }
        else {
            throw new Error(res.statusMessage);
        }
    });
}

後はHTTPレスポンスのステータスコードが200である場合にのみデータを適切な文字コードからutf-8に変換するだけです。変換した文字列をoption.dataに格納し、optionをプロミスの結果として返します。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2