3

Assume makeBurger() will take 10 seconds

In synchronous program,

function serveBurger() {
   makeBurger();
   makeBurger();
   console.log("READY") // Assume takes 5 seconds to log. 
}

This will take a total of 25 seconds to execute.

So for NodeJs lets say we make an async version of makeBurgerAsync() which also takes 10 seconds.

function serveBurger() {
   makeBurgerAsync(function(count) {

   });
   makeBurgerAsync(function(count) {

   });  
   console.log("READY") // Assume takes 5 seconds to log. 
}

Since it is a single thread. I have troubling imagine what is really going on behind the scene.

  1. So for sure when the function run, both async functions will enter event loops and console.log("READY") will get executed straight away.
  2. But while console.log("READY") is executing, no work is really done for both async function right? Since single thread is hogging console.log for 5 seconds.
  3. After console.log is done. CPU will have time to switch between both async so that it can run a bit of each function each time.

So according to this, the function doesn't necessarily result in faster execution, async is probably slower due to switching between event loop? I imagine that, at the end of the day, everything will be spread on a single thread which will be the same thing as synchronous version?

I am probably missing some very big concept so please let me know. Thanks.

EDIT It makes sense if the asynchronous operations are like query DB etc. Basically nodejs will just say "Hey DB handle this for me while I'll do something else". However, the case I am not understanding is the self-defined callback function within nodejs itself.

EDIT2

function makeBurger() {
    var count = 0;
    count++; // 1 time
    ...
    count++; // 999999 times
    return count;
}

function makeBurgerAsync(callback) {
    var count = 0;
    count++; // 1 time
    ...
    count++; // 999999 times
    callback(count);
}
Zanko
  • 4,298
  • 4
  • 31
  • 54
  • It depends on whether the makeBurgerAsync is more like setTimeout that postpones the execution in the event loop or more like await something that makes the contiunation execute only after the actual method completes. In the latter case, the console.log won't make it in 5 seconds, it will be there after both calls complete. – Wiktor Zychla Apr 18 '17 at 21:59
  • 1
    it depends on *why* it takes 10 seconds to complete the "async" action. If the 10 seconds is spent blocking the event loop, then yea it's going to block for 10 seconds, however it will only block *after* the console.log has occured because the action that takes 10 seconds would have been pushed off to the callback queue. (and it would block any request that come in during that 10 second window) If the 10 seconds is instead spent waiting on, for example, file i/o, or http, thus not blocking the event loop, there's no significant blocking. – Kevin B Apr 18 '17 at 22:01
  • @WiktorZychla ""more like await something that makes the contiunation execute only after the actual method completes" Can you clarify this statement? Thanks for your response! – Zanko Apr 18 '17 at 22:02
  • Clarificaton: there is no such thing like `fooAsync` in js. It's either `fooAsync(callback)` which returns immediately and executes the callback after it completes or `await fooAsync` that executes the next line after it completes. Your example `makeBurgerAsync` needs a clafitication on what kind of async execution you actually mean. – Wiktor Zychla Apr 18 '17 at 22:06
  • i mean... it doesn't *have* to give you the ability to know when it is done for it to be asynchronous @WiktorZychla. – Kevin B Apr 18 '17 at 22:08
  • @WiktorZychla sorry you are right. I have updated the question. Am I right to assume that in my particular case, asynchronous function doesn't help at all? – Zanko Apr 18 '17 at 22:10
  • @Zanko `makeBurgerAsync` isn't asynchronous. If the entire body of the function was wrapped in a setImmediate, it would be asynchronous (but it wouldn't be much better than the original) – Kevin B Apr 18 '17 at 22:11
  • Relevant: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop – Felix Kling Apr 18 '17 at 22:11
  • @KevinB why it is not asynchronous? If a callback is provided, it will have a chance to enter event loop right? Making it an async function? – Zanko Apr 18 '17 at 22:13
  • @Zanko: It's not asynchronous because you are not adding anything to the event loop (e.g. via `setTimeout`). Merely calling a function that was passed as parameter doesn't make a function asynchronous (`Array#map` is not asynchronous either). – Felix Kling Apr 18 '17 at 22:14
  • @Zanko It's all being run from the same callstack. A function is asynchronous only if it allows the event loop to run, in your case it will not allow the event loop to run. – Kevin B Apr 18 '17 at 22:14
  • I see, I guess that where I got confuse! Alright let's say I use setTimeout to make them async-function. In my case, it won't make those function run in the background right? I mean no performance boost for this case. – Zanko Apr 18 '17 at 22:25
  • Right, it simply delays execution. If you want to run long running jobs, have a look at something like https://github.com/Automattic/kue . – Felix Kling Apr 18 '17 at 22:44

1 Answers1

9

In node.js, all asynchronous operations accomplish their tasks outside of the node.js Javascript single thread. They either use a native code thread (such as disk I/O in node.js) or they don't use a thread at all (such as event driven networking or timers).

You can't take a synchronous operation written entirely in node.js Javascript and magically make it asynchronous. An asynchronous operation is asynchronous because it calls some function that is implemented in native code and written in a way to actually be asynchronous. So, to make something asynchronous, it has to be specifically written to use lower level operations that are themselves asynchronous with an asynchronous native code implementation.

These out-of-band operations, then communicate with the main node.js Javascript thread via the event queue. When one of these asynchronous operations completes, it adds an event to the Javascript event queue and then when the single node.js thread finishes what it is currently doing, it grabs the next event from the event queue and calls the callback associated with that event.

Thus, you can have multiple asynchronous operations running in parallel. And running 3 operations in parallel will usually have a shorter end-to-end running time than running those same 3 operations in sequence.

Let's examine a real-world async situation rather than your pseudo-code:

function doSomething() {
   fs.readFile(fname, function(err, data) {
       console.log("file read");
   });
   setTimeout(function() {
       console.log("timer fired");
   }, 100);

   http.get(someUrl, function(err, response, body) {
       console.log("http get finished");
   });

   console.log("READY");
}

doSomething();

console.log("AFTER");

Here's what happens step-by-step:

  1. fs.readFile() is initiated. Since node.js implements file I/O using a thread pool, this operation is passed off to a thread in node.js and it will run there in a separate thread.
  2. Without waiting for fs.readFile() to finish, setTimeout() is called. This uses a timer sub-system in libuv (the cross platform library that node.js is built on). This is also non-blocking so the timer is registered and then execution continues.
  3. http.get() is called. This will send the desired http request and then immediately return to further execution.
  4. console.log("READY") will run.
  5. The three asynchronous operations will complete in an indeterminate order (whichever one completes it's operation first will be done first). For purposes of this discussion, let's say the setTimeout() finishes first. When it finishes, some internals in node.js will insert an event in the event queue with the timer event and the registered callback. When the node.js main JS thread is done executing any other JS, it will grab the next event from the event queue and call the callback associated with it.
  6. For purposes of this description, let's say that while that timer callback is executing, the fs.readFile() operation finishes. Using it's own thread, it will insert an event in the node.js event queue.
  7. Now the setTimeout() callback finishes. At that point, the JS interpreter checks to see if there are any other events in the event queue. The fs.readfile() event is in the queue so it grabs that and calls the callback associated with that. That callback executes and finishes.
  8. Some time later, the http.get() operation finishes. Internal to node.js, an event is added to the event queue. Since there is nothing else in the event queue and the JS interpreter is not currently executing, that event can immediately be serviced and the callback for the http.get() can get called.

Per the above sequence of events, you would see this in the console:

READY
AFTER
timer fired
file read
http get finished

Keep in mind that the order of the last three lines here is indeterminate (it's just based on unpredictable execution speed) so that precise order here is just an example. If you needed those to be executed in a specific order or needed to know when all three were done, then you would have to add additional code in order to track that.


Since it appears you are trying to make code run faster by making something asynchronous that isn't currently asynchronous, let me repeat. You can't take a synchronous operation written entirely in Javascript and "make it asynchronous". You'd have to rewrite it from scratch to use fundamentally different asynchronous lower level operations or you'd have to pass it off to some other process to execute and then get notified when it was done (using worker processes or external processes or native code plugins or something like that).

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • This address my confusion so much! Thank you for such a detailed post. You can't take a synchronous operation written entirely in Javascript and "make it asynchronous". I can however, setTimeout on a synchronous function to give it a chance to enter event loop. But it does little good. I guess the point for asynchornous to communicate with other resources that will not hog the thread. – Zanko Apr 19 '17 at 02:34
  • You mentioned "fs.readFile() is initiated. Since node.js implements file I/O using a thread pool, this operation is passed off to a thread in node.js and it will run there in a separate thread." This makes me feel like node.js indirectly use more than one thread? – Zanko Apr 19 '17 at 02:35
  • @Zanko - When people say "single-threaded" in the content of node.js, they are talking about the JS interpreter., Your JS code only runs in a single thread and there can never be threading conflicts between two pieces of Javascript. There are library functions that use native code (of which `fs.readFile()` is one example) that do use threads in order to implement asynchronous functionality. That still doesn't make the JS interpreter multi-threaded in any way. The asynchronous functions communicate and synchronize with the single thread of the JS interpreter via the event queue. – jfriend00 Apr 19 '17 at 04:24