In pt 1, the code we used is blocking. What this means is that the current task needs to finish before the next one can start.
For example, say the first task of processing localhost:8888/start (which gets routed to the start function ) tasks 10 seconds to finish, you can simulate this by using a sleep(10).
From the Node Beginner book:
“Just to make clear what that does: when the function start() is called, Node.js waits 10 seconds and only then returns “Hello Start”. When calling upload(), it returns immediately, just like before.
(Of course, you should imagine that instead of sleeping for 10 seconds, there would be a real life blocking operation in start(), like some sort of long-running computation like reading a huge table from the db)
”
How to show this:
“First, open two browser windows or tabs. In the first browser window, please enter http://localhost:8888/start into the address bar, but do not yet open this url!
In the second browser window’s address bar, enter http://localhost:8888/upload, and again, please do not yet hit enter.
Now, do as follows: hit enter on the first window (“/start”), then quickly change to the second window (“/upload”) and hit enter, too.
What you will notice is this: The /start URL takes 10 seconds to load, as we would expect. But the /upload URL doesn’t process because its waiting for the /start URL to finish its 10 second process
Instead, whenever expensive operations must be executed, these must be put in the background, and their events must be handled by the event loop.
What’s going on: The 2 step: exec and finish block
So we spawn a child process to do the long time operation. However all we see is empty in our browser. The concept here is that the child process runs in 2 step. First it executes its task, then when it finishes, it executes its finish block and that’s it.
In our example, after the user hits “localhost:8888/start” in their browser, the main thread goes through the code 1, 2, and when it hits 3, spawns the child process. Then it goes straight down to 4, where the content variable is still “empty”.
When the child process finishes processing the “find” task, it executes its finish block and stops.
Hence that’s why you only see “empty” in your browser.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
console.log("start of router.js"); var exec = require("child_process").exec; function route(pathname) { // 1 console.log("router.js - about to route a request for " + pathname); // 2 var content = "empty"; if(pathname === '/start') { //3 exec("Find /Users/RickyTsao/Documents/JDBC", function (error, stdout, stderr) { console.log("------FIND operation done--------"); content = stdout; console.log("content is: " + content); }); console.log("routerl.js - returning content: " + content); return content; // 4 } else { return "------- request received -----------"; } } exports.route = route; console.log("end of router.js"); |
In other words, it’s clear that exec() does something in the background, while Node.js itself continues with the application, and we may assume that the callback function we passed into exec() will be called only when the “Find” command has finished running.
When you run the code, first, the server sets up:
Rickys-MacBook-Pro:nodeProject0 rickytsao$ node index.js
start of index.js
end of server.js
start of router.js
end of router.js
server variable running function start with parameter router
server.js – start function:
[Function: route]
server.js – Server has started. end of start function
end of index.js
Then you open your browser, run a random url like so:
localhost:8888/hehehehe
server.js – start of onRequest function
server.js – request.url: /heheheh
router.js – about to route a request for /heheheh
server.js – giving result to response obj….
Perfect, so we see that the onRequest function receives the url, gives it to router for process, then returns it to server.js for the response obj to display.
Then, hit the /start url like so:
localhost:8888/start
Okay, so you’ll see that the main thread goes through as usual, hits onRequest function, prints the logs, and as you can see at router.js, it returns “empty” string. It does so because at 4, the content variable still has the string “empty”. The separate child process has asynchronously run its own course to process our Find.
server.js – start of onRequest function
server.js – request.url: /start
router.js – about to route a request for /start
routerl.js – returning content: empty
server.js – giving result to response obj….
When it finishes that Find, it prints the finish block code.
——FIND operation done——–
content is: /Users/RickyTsao/Documents/JDBC
/Users/RickyTsao/Documents/JDBC/.DS_Store
/Users/RickyTsao/Documents/JDBC/Apress – Expert.Oracle.JDBC.Programming.pdf
/Users/RickyTsao/Documents/JDBC/JDBC Recipes- A Problem-Solution Approach %2528Problem-Solution Approach%2529%255B2005%255D.pdf
Therefore, even though we solved the blocking problem by using a child process to run it in a non-blocking way, we still need to make sure it returns the results correctly. We do so by injecting the response object into the start function.
Better yet, we should have all request handler functions be grouped into a requestHandler.js, which has the response obj injected. And whenever a child process finishes it task and runs to its finish block code, it can use response and correctly output the results.
We do all of this in part 3.
full code for part 2
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
console.log('start of index.js'); //get the module server, which is the server.js we just wrote //put it into the variable server var server = require("./server"); //get the router module, which is router.js we just wrote //put it into the variable router var router = require("./router"); //then have server variable call its function start //using parameter router's router function console.log('server variable running function start with parameter router'); server.start(router.route); console.log('end of index.js'); |
server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
//requres the http module that ships with Node.js and return it to //global variable http var http = require("http"); var url = require("url"); //have start function...like bootstrap //start is called by global variable server in index.js function start(route){ console.log('server.js - start function: '); console.log(route); //DEFINES a function called onRequest, to be used repeatedly //by global http's createServer function onRequest(request, response) { console.log('server.js - start of onRequest function'); console.log('server.js - request.url: ' + request.url); var pathname = url.parse(request.url).pathname; var result = route(pathname); console.log('server.js - giving result to response obj....'); response.writeHead(200, {"Content-Type": "text/plain"}); response.write(result); response.end(); } http.createServer(onRequest).listen(8888); console.log("server.js - Server has started. end of start function"); } exports.start = start; console.log('end of server.js'); |
router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
console.log("start of router.js"); var exec = require("child_process").exec; function route(pathname) { console.log("router.js - about to route a request for " + pathname); var content = "empty"; if(pathname === '/start') { exec("Find /Users/RickyTsao/Documents/JDBC", function (error, stdout, stderr) { console.log("------FIND operation done--------"); content = stdout; console.log("content is: " + content); }); console.log("routerl.js - returning content: " + content); return content; } else { return "------- request received -----------"; } } exports.route = route; console.log("end of router.js"); |