If you feel you must issue a synchronous HTTP request...
...as Ray Toal pointed out, there's an npm package that does that for you by offloading it to a child process and using spawnSync
to synchronously wait for that process to complete.
It has negative implications, which is why the advice is not to use synchronous I/O requests in Node, even though the Node API provides a few (fileReadSync
, etc.). Node uses a single JavaScript thread. By blocking that thread on a long-running process (an HTTP request), you prevent that thread from doing anything else while the request is being processed.
If you just want to write synchronous-looking code...
...I'd suggest using promises and async
/await
syntax instead. async
/await
syntax brings asynchronous processing to code that looks synchronous, providing the benefits of asynchronous processing with synchronous-like syntax. Recent versions of Node use recent versions of the V8 JavaScript engine, which supports async
/await
.
Node predates Promises, and uses its own API conventions for callbacks, but there are packages such as promisify that can convert Node-callback style APIs to Promise-based ones. promisify
works at the API level, so with a couple of lines of code you can convert the entire fs
module's API for instance. As time moves forward, we can expect promises to be built-in to new packages (and I suspect retrofitted to the standard Node API as well).
For http.request
, it's a bit more complicated than just updating the API as there are events to respond to. But "a bit more" is all it is. Here's a quick-and-simple version which also adds automatic handling of the Content-Length
header:
const requestPromise = (options, postData = null) => new Promise((resolve, reject) => {
const isPostWithData = options && options.method === "POST" && postData !== null;
if (isPostWithData && (!options.headers || !options.headers["Content-Length"])) {
// Convenience: Add Content-Length header
options = Object.assign({}, options, {
headers: Object.assign({}, options.headers, {
"Content-Length": Buffer.byteLength(postData)
})
});
}
const body = [];
const req = http.request(options, res => {
res.on('data', chunk => {
body.push(chunk);
});
res.on('end', () => {
res.body = Buffer.concat(body);
resolve(res);
});
});
req.on('error', e => {
reject(e);
});
if (isPostWithData) {
req.write(postData);
}
req.end();
});
With that in our toolkit, we can make an asynchronous request within an async
function like this:
try {
const res = await requestPromise(/*...options...*/, /*...data if needed...*/);
console.log(res.body.toString("utf8"));
// ...continue with logic...
} catch (e) {
console.error(e);
}
As you can see, the logical flow of the code is as it would be for synchronous code. But the code is not synchronous. The JavaScript thread runs the code up to and including the expression that's the operand of the first await
, then does other things while the asynchronous process runs; later when the asynchronous process is complete, it picks up where it left off, assigning the result to res
and then executing the console.log
, etc. — up until the next await
(if any). If the result of the promise await
consumes is a rejection, it's processed as an exception and passed to the catch
in the try
/catch
.
Here's the example from http.request
using our requestPromise
above:
try {
const res = await requestPromise(
{
hostname: 'www.google.com',
port: 80,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
},
querystring.stringify({
'msg': 'Hello World!'
}
);
console.log(res.body.toString("utf8"));
} catch (e) {
console.error(e);
}
To use await
, you have to be in an async
function. You'd probably just wrap your entire module code in one:
(async () => {
// ...module code here...
})();
If there's any possibility of your code not catching an error, add a catch-all catch
handler at the end:
(async () => {
// ...module code here...
})().catch(e => { /* ...handle the error here...*/ });