Nodejs-如何保证 http.request? 拒绝被调用了两次

我正试图把 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,它将调用 第一个拒绝,并在一段时间后 第二次拒绝。如何正确地使它只调用一个拒绝(我认为 第一个拒绝是正确的在这种情况下) ?我认为我需要关闭 res自己,但没有 close()方法对 ClientResponse对象。

UPD: 第二次拒绝很少触发-为什么?

102279 次浏览

It's easier for you to use bluebird api, you can promisify request module and use the request function async as a promise itself, or you have the option of using the module request-promise, that makes you to not working to creating a promise but using and object that already encapsulates the module using promise, here's an example:

var rp = require('request-promise');


rp({host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'})
.then(function (parsedBody) {
// GET succeeded...
})
.catch(function (err) {
// GET failed...
});

Your code is almost fine. To restate a little, you want a function that wraps http.request with this form:

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()
});
}

Notice the addition of params and postData so this can be used as a general purpose request. And notice the last line req.end() -- which must always be called -- was missing from the OP code.

Applying those couple changes to the OP code...

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();
});
}

This is untested, but it should work fine...

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);
});

And these promises can be chained, too...

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

I know this question is old but the answer actually inspired me to write a modern version of a lightweight promisified HTTP client. Here is a new version that:

  • Use up to date JavaScript syntax
  • Validate input
  • Support multiple methods
  • Is easy to extend for HTTPS support
  • Will let the client decide on how to deal with response codes
  • Will also let the client decide on how to deal with non-JSON bodies

Code below:

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();
});
}

Hope this help.

const request = require('request');


async function getRequest() {
const options = {
url: 'http://example.com',
headers: {
'Authorization': 'Bearer xxx'
}
};


return new Promise((resolve, reject) => {
return request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
const json = JSON.parse(body);
return resolve(json);
} else {
return reject(error);
}
});
})
}

There are other ways as well but here you can find a simple way to make http.request as a promise or async/await type.

Here is a working sample code:

var http = require('http');


function requestAsync(name) {


return new Promise((resolve, reject) => {
var post_options = {
host: 'restcountries.eu',
port: '80',
path: `/rest/v2/name/${name}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
let post_req = http.request(post_options, (res) => {
res.setEncoding('utf8');
res.on('data', (chunk) => {
resolve(chunk);
});
res.on("error", (err) => {
reject(err);
});
});
post_req.write('test');
post_req.end();
});
}


//Calling request function
//:1- as promise
requestAsync("india").then(countryDetails => {
console.log(countryDetails);
}).catch((err) => {
console.log(err);
});


//:2- as await
let countryDetails = await requestAsync("india");

After reading all of these and a few articles, I thought I'd post a sort of "general" solution that handles both http and https:

const http = require("http");
const https = require("https");
const url_obj = require("url");


const request = async (url_string, method = "GET", postData = null) => {
const url = url_obj.parse(url_string);
const lib = url.protocol=="https:" ? https : http;
const params = {
method:method,
host:url.host,
port: url.port || url.protocol=="https:" ? 443 : 80,
path: url.path || "/"
};
return new Promise((resolve, reject) => {
const req = lib.request(params, res => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error(`Status Code: ${res.statusCode}`));
}
const data = [];
res.on("data", chunk => {
data.push(chunk);
});
res.on("end", () => resolve(Buffer.concat(data).toString()));
});
req.on("error", reject);
if (postData) {
req.write(postData);
}
req.end();
});
}

You could use like this:

request("google.com").then(res => console.log(res)).catch(err => console.log(err))

This is heavily inspired by this article, but replaces the hacky url parsing with the built in api.