web-dev-qa-db-ja.com

nodejs-http.requestを約束する方法は?拒否が2回呼び出されました

_http.request_をPromiseにラップしようとしています:

_ new Promise(function(resolve, reject) {
    var req = http.request({
        Host: '127.0.0.1',
        port: 4000,
        method: 'GET',
        path: '/api/v1/service'
    }, function(res) {
        if (res.statusCode < 200 || res.statusCode >= 300) {
            // First reject
            reject(new Error('statusCode=' + res.statusCode));
            return;
        }
        var body = [];
        res.on('data', function(chunk) {
            body.Push(chunk);
        });
        res.on('end', function() {
            try {
                body = JSON.parse(Buffer.concat(body).toString());
            } catch(e) {
                reject(e);
                return;
            }
            resolve(body);
        });
    });
    req.on('error', function(err) {
        // Second reject
        reject(err);
    });
    req.write('test');
}).then(function(data) {
    console.log(data);
}).catch(function(err) {
    console.log(err);
});
_

リモートサーバーからエラーstatusCodeを受信した場合、最初の拒否を呼び出し、少し時間が経過した後、2番目の拒否を呼び出します。適切に作成して単一のリジェクトのみを呼び出す方法(この場合、最初のリジェクトが適切なものだと思います)? resを自分で閉じる必要があると思いますが、ClientResponseオブジェクトにはclose()メソッドがありません。

PD: 2番目の拒否トリガーは非常にまれです-なぜですか?

36
happy_marmoset

コードはほとんど問題ありません。少し言い換えると、http.requestを次の形式でラップする関数が必要です。

_function httpRequest(params, postData) {
    return new Promise(function(resolve, reject) {
        var req = http.request(params, function(res) {
            // on bad status, reject
            // on response data, cumulate it
            // on end, parse and resolve
        });
        // on request error, reject
        // if there's post data, write it to the request
        // important: end the request req.end()
    });
}
_

paramspostDataが追加されていることに注意してください。これにより、これを汎用リクエストとして使用できます。 最後の行に注意してくださいreq.end()-常に呼び出す必要があります-OPコードにありませんでした。

これらの変更をOPコードに適用しています...

_function httpRequest(params, postData) {
    return new Promise(function(resolve, reject) {
        var req = http.request(params, function(res) {
            // reject on bad status
            if (res.statusCode < 200 || res.statusCode >= 300) {
                return reject(new Error('statusCode=' + res.statusCode));
            }
            // cumulate data
            var body = [];
            res.on('data', function(chunk) {
                body.Push(chunk);
            });
            // resolve on end
            res.on('end', function() {
                try {
                    body = JSON.parse(Buffer.concat(body).toString());
                } catch(e) {
                    reject(e);
                }
                resolve(body);
            });
        });
        // reject on request error
        req.on('error', function(err) {
            // This is not a "Second reject", just a different sort of failure
            reject(err);
        });
        if (postData) {
            req.write(postData);
        }
        // IMPORTANT
        req.end();
    });
}
_

これはテストされていませんが、うまくいくはずです...

_var params = {
    Host: '127.0.0.1',
    port: 4000,
    method: 'GET',
    path: '/api/v1/service'
};
// this is a get, so there's no post data

httpRequest(params).then(function(body) {
    console.log(body);
});
_

そして、これらの約束も連鎖させることができます...

_httpRequest(params).then(function(body) {
    console.log(body);
    return httpRequest(otherParams);
}).then(function(body) {
    console.log(body);
    // and so on
});
_
54
lara

私はこの質問が古いことを知っていますが、答えは実際に軽量の約束されたHTTPクライアントの最新バージョンを書くように私を刺激しました。新しいバージョンは次のとおりです。

  • 最新のJavaScript構文を使用する
  • 入力を検証する
  • 複数の方法をサポート
  • HTTPSサポート用に簡単に拡張できます
  • クライアントに応答コードの処理方法を決定させます
  • また、クライアントがJSON以外の組織の対処方法を決定できるようにします

以下のコード:

function httpRequest(method, url, body = null) {
    if (!['get', 'post', 'head'].includes(method)) {
        throw new Error(`Invalid method: ${method}`);
    }

    let urlObject;

    try {
        urlObject = new URL(url);
    } catch (error) {
        throw new Error(`Invalid url ${url}`);
    }

    if (body && method !== 'post') {
        throw new Error(`Invalid use of the body parameter while using the ${method.toUpperCase()} method.`);
    }

    let options = {
        method: method.toUpperCase(),
        hostname: urlObject.hostname,
        port: urlObject.port,
        path: urlObject.pathname
    };

    if (body) {
        options.headers['Content-Length'] = Buffer.byteLength(body);
    }

    return new Promise((resolve, reject) => {

        const clientRequest = http.request(options, incomingMessage => {

            // Response object.
            let response = {
                statusCode: incomingMessage.statusCode,
                headers: incomingMessage.headers,
                body: []
            };

            // Collect response body data.
            incomingMessage.on('data', chunk => {
                response.body.Push(chunk);
            });

            // Resolve on end.
            incomingMessage.on('end', () => {
                if (response.body.length) {

                    response.body = response.body.join();

                    try {
                        response.body = JSON.parse(response.body);
                    } catch (error) {
                        // Silently fail if response is not JSON.
                    }
                }

                resolve(response);
            });
        });

        // Reject on request error.
        clientRequest.on('error', error => {
            reject(error);
        });

        // Write request body if present.
        if (body) {
            clientRequest.write(body);
        }

        // Close HTTP connection.
        clientRequest.end();
    });
}
2