So I wasted a bunch of time writing some code like this:
function processResponse(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body);
} else {
console.error(util.inspect(response, false, null));
}
waiting = false;
};
request.get(requestOpts.url, processResponse);
console.log("Waiting");
while(waiting) {
count += 1;
if(count % 10000000 == 0) {
console.log(count);
}
}
I was trying to get node to wait (and not exit) until the reponse came back from the webserver. Turns out, this didint' work, and what did work was doing nothing. Just:
request.get(requestOpts.url, processResponse);
How did request keep node from exiting while the callback was pending?
To handle the asynchronous nature of JavaScript executions, you can use one of the three available methods to wait for a function to finish: This tutorial will help you learn all three methods, starting from using callback functions A callback function is a regular JavaScript function that you pass to another function.
Note that async function and await is define in ECMAScript 2017 Draft (ECMA-262) which is not final yet at the time of this writing as of March 2017 (it will be in June 2017). But it's already available in Node since v7.6 (and it was available since v7.0 if you used the --harmony flag). For the compatibility with Node versions, see:
Only when JavaScript is done running all its synchronous code, and is good and ready, will the event loop start picking from the queues and handing the functions back to JavaScript to run. So let's take a look at an example:
JavaScript code execution is asynchronous by default, which means that JavaScript won’t wait for a function to finish before executing the code below it. For example, consider the following function calls:
Node always keep track of any pending callbacks and will not exit until that hits zero. This will include all active network connections/requests as well as filesystem IO and subprocess activity. There's nothing special you need to code to get the expected behavior. node will do what you expect in this case by default.
TLDR
In the code from the OP, the synchronous while-loop prevents the event loop from ever reaching the poll phase, so the event loop gets stuck on its first tick and the network I/O is never processed.
Complete answer
Node.js's asynchronous programming model is based on a synchronous loop called the event loop. The basic abstraction of the event loop is function scheduling: a function in Node.js is able to schedule other functions (like network request handlers and timers) to run at some point in the future and in response to some event.
The event loop is basically a continuously running "while loop". During each "tick" of the loop, the Node.js runtime checks to see whether the condition for a scheduled function is met -- if the condition is met (like if a timer has elapsed), the function is executed.
The event loop processes callbacks in a particular order.
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
The only place for network requests to be processed is during the poll phase; so in order for a network request to be processed by a callback, the event loop must reach the poll phase.
Each stage of the event loop is only able to advance after the synchronous functions from the previous stage have finished running -- i.e. they have returned, and the call stack is empty.
In the code from the OP, the synchronous while-loop prevents the event loop from ever reaching the poll phase, so the network request handler is never executed.
Looping while waiting on a network request
In order to run code in a loop while we wait on the network request, we need to somehow give the event loop an opportunity to process other callbacks.
We can achieve this by running our loop in a callback scheduled via setTimeout()
. setTimeout()
is a mechanism for scheduling functions to run during the timers
phase of the event loop.
Each time our loop finishes, the event loop has an opportunity to process new I/O events. If there's no new I/O event, it will move on to the next setTimeout()
handler scheduled via loop()
.
const request = require('request')
let count = 0
let isFinished = false
request.get('https://www.google.com', (err, res) => (isFinished = true))
console.log('Waiting')
loop()
function loop () {
setTimeout(
() => {
count += 1
if (isFinished) {
console.log('done')
return
}
if(count % 10 == 0) {
console.log(count);
}
return loop()
},
0
)
}
In this example, we keep "recursively" calling loop
until the network request has finished. Each time the setTimeout()
handler (scheduled by loop()
) returns, the event loop moves beyond the timers
phase and checks for new network I/O (i.e. our request to Google). As soon as the response is ready, our network request handler is called during the poll
phase.
Even though loop()
is "recursive", it doesn't increase the size of the call stack. Since each loop iteration is scheduled via setTimeout
, the loop()
function won't push another loop()
call onto the call stack until the setTimeout()
handler is run in the next timers
phase - at which point the call stack from the previous loop()
call will already be cleared.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With