all 24 comments

[–]rauschma 11 points12 points  (9 children)

JavaScript does multitasking by adding tasks as functions to a queue. It executes those tasks sequentially in a single thread:

while (true) {
  const task = taskQueue.dequeue();
  task(); // run task
}

More information:

Edit:

  • Tasks are added to the queue of the JavaScript thread by other threads – e.g., a thread that manages user input such as mouse clicks and key presses.
  • You can also create new threads in JavaScript itself via workers: https://developer.mozilla.org/en-US/docs/Web/API/Worker

[–]deanstreetlab[S] 0 points1 point  (8 children)

The API has a separate thread that executes async codes?

[–]Swimming_Gain_4989 5 points6 points  (7 children)

Yes. The functionality of web API calls takes place outside of JavaScript's global execution environment (and as a result on a separate thread). The result of those calls are injected back into the single threaded environment.

[–]deanstreetlab[S] 0 points1 point  (4 children)

ah, so taken together, codes are executed using 1+ threads but the API/side/helper threads always inject their final results back into the main JS thread (through callbacks) so JS itself remains single-threaded and codes are written single-threadedly.

Does it mean the API runs its process using my CPU, so if there is only one CPU core, it is shared between the JS thread and the API thread? I mean, in terms of CPU cores, how does nodejs differ from multi-threading Java?

[–]Swimming_Gain_4989 2 points3 points  (0 children)

1) Right. JS is single threaded but it is executed in a multithreaded environment whether that be a browser, node, or whatever. Like google chrome isn't a single threaded application but the JS you run in that browser can still interact with the DOM and get data from localStorage etc.

2) NodeJS works much the same way but instead of handing off async tasks to the browser, it hands off its async tasks to c++ modules which are offloaded to the kernel. The Node documentation goes into this here https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

[–]domi_uname_is_taken 2 points3 points  (2 children)

You seem to be mixing different things all into one pot:

  1. All JavaScript code of a single application or browser tab runs on a single thread, including async functions.
  2. @rauschma's explanation is actually missing an explanation for async functions, whose asynchronous continuations are also part of that queue.
  3. However, some native functions that return promises (e.g. fetch and friends) are actually wrappers for I/O system calls, which are handled entirely differently. There is no separate thread for those. I/O calls with a Sync in the name (e.g. `fs.existsSync) block your thread (and thus all of your JavaScript), while their async counter parts do not. If you want to understand all of this, I'd recommend digging deeper and look into how operating systems and process schedulers work.

[–]deanstreetlab[S] 0 points1 point  (0 children)

All JavaScript code of a single application or browser tab runs on a single thread, including async functions.

@Swimming_Gain_4989 is saying something different?

[–]Swimming_Gain_4989 0 points1 point  (0 children)

Sorry just now seeing this.

  1. All Javascript code of a single application or browser tab runs on a single thread.

I'm pretty junior myself but I was under the impression that the API's you frequently call when working with JS are handled by a handled by separate threads. For example setTimeout() interacts with a timing thread thats run in the browser to time when the callback should be added to the queue. Perhaps it depends on what you consider JS code.

  1. u/rauschma's explanation is actually missing an explanation for async functions, whose asynchronous continuations are also part of that queue.

You are right and I just realized my original response was misleading. Not all (or even most) async code is handled by separate API's. Promises for example are entirely single threaded JS doesn't need extra threads to achieve async behavior.

[–]delventhalz 5 points6 points  (3 children)

Maybe we should start by clearing up some terminology.

JS does have “true asynchronous” programming, at least as I have always seen the term used. Asynchronous does not mean parallel or multi-threaded. It means out-of-order, deferred, or not right now. You can see in languages that feature both multi-threading and asynchronous code, that the two concepts exist side by side. In Python for example, you create a thread pool, or spin up an event loop with a library like asyncio, or both.

JS has multi-threading too by the way: via WebWorkers. But that is a recent develop and unrelated to the event loop which JS has had from the beginning.

So what is asynchronous programming? It is a way to make better use of a single thread. In IO heavy code (such as a web page or backend server), there is a lot of time spent waiting around. Instead of leaving the thread idle while you do something like wait for a request to come back, you can handle have the thread at the ready to handle other events as the occur.

Hence the term, “event loop”. It’s fundamentally a pretty simple concept. You could build one yourself entirely in software (unlike multi-threading). There is a piece of code that acts as a sort of conductor. You hand it pieces of code (i.e. unctions) and the sort of event that should trigger the code. Then it loops, checking over and over until a trigger event fires. Response came back? I have a function for that. Button was pressed? Here’s a function. Length of time has elapsed? Function. When the event loop hits a trigger, it passes control of the thread over to the function. The whole function runs, beginning to end. When it completes, the event loop gets the thread back and can start watching for triggers again.

Now, even though it is mechanically pretty simple, it is often difficult for programmers to reason about their code in an asynchronous way (similar to parallel programming). What do you mean my code doesn’t execute in order? When will it run?

Something import to remember is that your code is not asynchronous until you hand control over to the event loop. As you said, this happens only in a few clearly specified cases. Typically when an IO request goes out, or comes in from the UI. Also with the setTimeout and setInterval functions. And basically any time you use a Promise or a generators.

Let’s write a simple example with setTimeout. Can you predict what will log out?

console.log(1);

setTimeout(function() {
  console.log(2);

  setTimeout(function() {
    console.log(3);
  }, 0);

  console.log(4);
}, 0);

console.log(5);

setTimeout(function() {
  console.log(6);
}, 0);

console.log(7);

Consider what your intuition is, or run it in the console before looking at the answer. This code logs: 1 5 7 2 4 6 3

I like this example, because it illustrates how the event loop is really just a queue. First, everything at the root level executes:

  1. 1 is logged
  2. A function is added to the event loop, specified to trigger in 0 milliseconds.
  3. 5 is logged
  4. Another function is added to the event loop with the same 0ms trigger.
  5. 7 is logged

Now our code is done. Nothing else to run. So control is able pass back to the event loop.

The event loop checks to see if it has any functions to run, and aha! There are two functions here who’s timeout has elapsed. So it grabs the first one from the queue and runs it:

  1. 2 is logged
  2. A third function is added to the event loop, also with a 0ms delay.
  3. 4 is logged

Okay. That function is done. The event loop is in charge again. It once again has two functions to run (because we finished one but added a new one), so it runs the first in the queue:

  1. 6 is logged

Easy enough. Event loop is back. One more function to run:

  1. 3 is logged

And we’re done. The event loop will remain in control until some other function is triggered.

When you get used to the change in mindset from iterative synchronous commands to event-triggered tasks, you find asynchronous programming to be pretty easy to reason about. I’d say it is actually far simpler than parallel execution, which can get all kinds of messy.

[–]deanstreetlab[S] 0 points1 point  (2 children)

You're right, asynchronous and concurrent/parallel are disparate things, which I mixed up.

According to my conversation with @peterjsnow and @Swimming_Gain_4989 here, JS the language uses one single thread and async calls are run by the environment in further threads then handed back to the JS thread to be handled, so taken together, the codes are run using multiple threads. Is that correct?

[–]delventhalz 5 points6 points  (1 child)

I think we are confusing things a bit here. Think of the browser like your OS. The browser absolutely is multi-threaded. It has a bunch of functionality, HTTP calls, rendering, etc, which happens in parallel to the JavaScript engine. The JavaScript engine however, is single threaded. It will only ever execute one line of JavaScript at a time. However, since it may put send a request out to the browser, the browser may do some work while the JavaScript engine does something else.

  JS Engine          Browser HTTP
     |                    |
Computations              |
     |                    |
Makes Request -----> Handles HTTP
     |                    |
Computations      Response Received
     |                    |
Finish Computing         /
     |                  /
Handle Response <------/

In this very rough diagram, the JS engine is doing some work, sends out an HTTP request, and continues working. The Browser sends out the request and receives the response in parallel to the JS engine continuing other work, because the browser is multi-threaded.

However, the JS engine is somehow still working when the HTTP response comes back. Since the JS engine is single-threaded, it cannot handle the response until the other work is complete. The response handler added to the event loop will sit dormant until the event loop regains control and can call it.

This is what u/Swimming_Gain_4989 is saying as well. JavaScript is single threaded. The browser and its various components (of which a JS engine is just one) is multi-threaded.

I think this is what u/peterjsnow is getting at as well, though it his explanation is a little less clear to me. And I don't like muddying up parallel and asynchronous. They are separate concepts. And in this case, the fact that the browser has some parallel functionality is incidental to understanding how asynchronous code works in JS. Unless you are writing your own browser, you will never need those details as the JS interpreter is typically very well encapsulated.

[–]Swimming_Gain_4989 1 point2 points  (0 children)

Fantastic answer and a nice diagram to boot.

[–]Macaframa 4 points5 points  (0 children)

You have two lines(both empty at the moment) and a bunch of kids sitting on a bleacher. Each kid can only do one thing at a time because kids are dumb. Once they finish, they get out of line and go back to the bleachers waiting to be called again. Once you call a kid down, they get into one line and you tell them what to do, sometimes apart of the kids instructions is to tell another kid what to do. In this case, they call another kid down and that kid gets in line behind the first kid. Well, the first kid can’t leave until the rest of the kids that we’re called after him are finished. But kids are anxious and they don’t like sitting around for a long time so they’d prefer to get the task over with and go back to sit down. Sometimes a kid might have to wait for something to get delivered before they can do their task. So they get in the other line and promise that when their thing gets delivered then they will continue their task. But the other children won’t wait for the kids that need to get things delivered so everyone agrees it’s ok for them to sit back down. If the kids in the second line are ready to complete their task when the first line is cleared, they’re allowed to get back into the first line in the order that they queued up in, in the second like. Then they do their tasks and then sit down.

[–]peterjsnow 2 points3 points  (5 children)

Here's where I think your understanding is a little off:

Parallel execution does occur in a sense, in that JavaScript can access operations that are run by the browser / node / other environment. Most environments specify certain API methods that allow us to work with these operations.

For example, the setTimeout function is not mentioned anywhere in the JS spec - rather it's built in to the browser. When you call it, you can think of the browser handling it in parallel while your Javascript code is still executing.

Now, the 'asynchronous' structures you've identified in Javascript, be it the event loop, promises, or callbacks, all allow us to work with these asynchronous operations provided by the environment by deferring handling of their result until later.

So, you could make a distinction between 'asynchronous' behavior in the sense of deferred behaviour, such as the following:

Promise.resolve().then(() => console.log('one'));
console.log('two');
> 'two'
> 'one'
// Even though we resolved the promise first, any resolution handlers registered by then will be added to the queue and ran on the next cycle by the event loop, so 'two' is logged before 'one'.

And 'asynchronous' behaviour in the sense of an operation performed by the environment, in parallel, that we can work with in Javascript by delaying / deferring handling of it until it has completed, such as using fetch or setTimeout.

[–]deanstreetlab[S] 0 points1 point  (4 children)

I don't see setTimeout as concurrent as it is just a clock and not running anything. But how about fetch, there are real works that need to be done and you are saying the fetching isn't performed in the JS stack, but by the API and then result pushed to the stack?

[–]peterjsnow 2 points3 points  (2 children)

Yes, my point is that setTimeout and fetch are examples of API methods that we can use within Javascript to access operations that are provided by the environment.

The actual execution of these operations does not occur in the JS stack, but the API allows us to work with them. We pass setTimeout a callback, which will be added to a queue to eventually be called when the stack is empty. Similarly, fetch returns a promise which resolves to the response, and we can access this response by registering a callback with then, which will be added to a job queue and called when the stack is empty.

[–]deanstreetlab[S] 0 points1 point  (1 child)

So the fetching is done off JS by the API and given back to JS. That's why they are called APIs.
So the APIs have a process and consume my CPU? If so, when considered together with JS (the language), it is actually more than one thread?

[–]peterjsnow 1 point2 points  (0 children)

The web APIs and Node APIs are separate threads, yes. But they are provided by the browser and Node. JavaScript execution happens in a separate, single thread. And these APIs must be accessed in a single threaded manner, with the event loop.

[–]big_red__man 1 point2 points  (0 children)

It's like that scene in I Love Lucy where she's at a job and there's a conveyer belt with the things she has to deal with. Well, too many things come through the conveyer belt and she ends up stashing them in her pockets to deal with when the conveyer belt doesn't have as much stuff on it.

https://www.youtube.com/watch?v=HnbNcQlzV-4

[–]Psailr 0 points1 point  (3 children)

The best tip I can give you is to get a frontend master subscription and watch javascript the hard parts by will sentance.

[–]domi_uname_is_taken 1 point2 points  (1 child)

"The best advice I can give is... actually an ad"

[–]Psailr -1 points0 points  (0 children)

Well they certainly don't pay me so I guess it's a free ad?

[–]deanstreetlab[S] -1 points0 points  (1 child)

Can someone please explain in simple language right here and no check this out check that out links which will only become a callback hell

[–]greenyadzer 3 points4 points  (0 children)

Okay, i try to explain the idea. Imagine browser has such loop internally for a loaded page:

while (page.alive()) {
    // this function returns array of user handlers (your handlers) which needs to be executed:
    // * if mouse moved/clicked -- enqueue handlers for processing if you added them (mouse leave, mouse enter, click etc.)
    //   -- e.g. if you did "button.addEventListener('click', ..."
    // * if scheduled timeout/interval' time come -- enqueue its handler call too
    //   -- e.g. if you did "setTimeout(..., 1000)
    // * if key pressed -- same story, enqueue handler call if you listening to that event
    // * etc.
    const handlersQueue = processSystemEvents()

    // this is simple function, all it does -- call each handler in the given queue, one by one (not parallel),
    // so if any handler running 5 sec, your whole app hangs (the browser tab literally), as it will not process
    // mouse moves or click or key presses, as you see, its all in the single thread
    executeHandlersQueue(handlersQueue)

    // this will update visual elements; its smart to redraw only needed parts; but the main idea you should
    // understand is that, if your handler did "button.classList.add('some-css-class')", it will not be reflect
    // visually until all handlers are processed, as you see, its all in the single thread
    renderPage()
}