DEV Community

loading...
Cover image for Javascript - Synchronous by nature

Javascript - Synchronous by nature

Arika O
WOP
Updated on ・4 min read

[EDIT] Because some commenters mentioned that the article is misleading and that Javascript is actually async: while we can write Javascript code in an async manner and I will be discussing all aspects like callbacks, promises, async/ await in the next articles, I believe it's importat to first understand how Javascript works out of the box before understanding its async capabilities.

I recently decided to dig deeper into how Javascript actually works, so I thought, why not create a series of articles explaining concepts like engines, single threaded, async, event loops (and more) in a simple way and share it with others? One thing I noticed about some of the learning resources out there is that they focus too much on writing code and too little on explaining the hows and whys of a programming language. Because of that, waking up with knowledge gaps it's almost inevitable so I'm hoping these articles will help me (and you) fill some of those gaps.

If along the way, I miss to mention something or you feel I wrongly/ incompletely explained some of the concepts, feel free to give me a heads up in the comments and I'll rectify the errors. Let's start.


ABOUT JAVASCRIPT
Although Javascript is mostly presented as a scripting language for web browsers, it can live and work very well in non browsers environments like AdobeAcrobat (where it's used for form handling), Apache CouchDB (a NoSQL type database where Javascript it's used as its query language) or NodeJs (a back-end run time environment which lets us run Javascript outside browsers). For now, I'm going to primarely focus on Javascript used in the browser.

The first things beginners learn about Javascript is that it's an interpreted, single-threaded, non-blocking and asynchronous programming language. What do these things mean?

INTERPRETED
Means that it needs an interpereter to be translated into machine code (code that computers understand). At the opposite end we have compiled languages, which don't need an interpreter (for example C or C++). Interpereters ar commonly referred to as "engines". Each browser uses a different engine to translate Javascript as the most popular ones are V8 for Chrome, Webkit JavaScriptCore for Safari and Quantum for Firefox. Whenever a browser receives JS code, the engine starts parsing it. It first checks for errors, and if none are found, it starts reading the code from top to bottom.

An list of all the task the V8 engine can take care of, might look like this:

  1. Compiles and executes JS code
  2. Handles the call stack (runs our JS functions in some order)
  3. Managing memory allocation for objects (the memory heap)
  4. Garbage collection (of objects which are no longer in use)
  5. Provide all the data types, operators, objects and functions

Don't worry about what all these things mean, they will all be explained in this series.


SINGLE THREADED
Means that Javascript can only do one thing at a time (it has one call stack and one memory heap - we'll explain these later), it can't multi-task. The opposite of this is multithreaded, which means that multimple parts of an application can be run at the same time.

SYNCHRONOUS
Although presented as a non-blocking, asynchronous programming language, Javascript follows a synchronous execution model. As the name suggests, synchronous means to be in a sequence, i.e. every statement of the code gets executed one by one. So, basically a statement has to wait for the earlier statement to get executed. If we take the code below:

console.log(1);
console.log(2);
console.log(3);
Enter fullscreen mode Exit fullscreen mode

it will print, as expected:

1
2
3
Enter fullscreen mode Exit fullscreen mode

For the second console.log to happen, the first needs to finish and for the third to happend, the second needs to finish. This is how synchronicity works. When we have small pieces of code, this isn't a problem, but if we have expensive operations, something called a blocking bahaviour will become apparent.

BLOCKING
By default, Javascript functions are blocking. This means that if we have a function that will take a long time to execute, the browser will get stuck and nothing else can be accessed on the webpage until the expensive function doesn't end execution. To see this in action, open any web page, then go to the console and run the function bellow. If you try to interact with the browser while the expensiveFunction is running, you will see this is not possible (not only the webpage you just opened it's unresponsive but the whole browser).

const expensiveFunction = () => {
 for (let i = 0; i < 1000; i++) {
      for (let j = 0; j < 1000; j++) {
        console.log(`i = ${i} and j = ${j}`);
      }
    }
}

expensiveFunction();
Enter fullscreen mode Exit fullscreen mode

In the next article we'll start talking about the complete Javascript runtime environment (which means much more than the Javascript engine), I'll draw a scheme of how things work and interact with eachother and explain two very important concepts: the stack and the heap talk about async code.

Image source: *Mati Mango/ @mati on Pexels

Discussion (25)

Collapse
valeriavg profile image
Valeria

I find the article misleading. The fact that statements are not executed in parallel is a direct result of a single thread architecture. Yet the ability to handle asynchronous operations is what made JavaScript so popular in the first place. Heavy synchronous operation will block the whole thread in any language, therefore it's the asynchronous operations that are taken into consideration.

Collapse
maixuanhan profile image
Han Mai

True. Wrong definition of “blocking”. Blocking is not the nature of a language or any applications. Blocking is when we talk about how a function or an application handle a task. A blocking function will block the thread until the task has been finished and results of the task can be obtained right after the code line. A non-blocking on the other hand will not block the thread but it will not return the results of the task right after the code line (and may return at some point in the future). By this definition, blocking/non-blocking is not the nature of any languages at all. It is the way how tasks have been handled. You can say blocking operations, blocking methods, blocking functions. Nobody says blocking languages.

Collapse
darkwiiplayer profile image
DarkWiiPlayer

You're kind of conflating two properties here by not clearly distinguishing between what kind of thread you're talking about. Many languages use "blocking" subroutine semantics, where calling a method pauses the code until it is completed, but don't necessarily pause the whole OS thread, which can be running several light threads at the same time.

async shows this quite well: it calls an asynchronous function in a blocking manner, meaning the code will wait until the operation is complete, but in the bigger picture, the OS-level browser thread isn't blocked and can continue running other asynchronous JS code.

Thread Thread
maixuanhan profile image
Han Mai

As I know, blocking means the current thread (the thread that is executing the code) stops there and wait for the operation or function which is handled by other thread or other program or kernel is completed. The thread in that blocking state will not consume CPU. It just hang in there. That is the only meaning of thread blocking. A JavaScript program is a single threaded application, that is the kind of thread that we are discussing.

“async”, “await” on the other hand are just sugar syntactic of callback functions. Under the hood, they may be transformed into some callback registration. There is no actually waiting. The event loop of the program will not be blocked and continue on.

Thread Thread
darkwiiplayer profile image
DarkWiiPlayer

The point is that when you await a function, the next line of code will not be executed until the asynchronous function finishes, therefore that thread of execution is blocked, whereas the OS-level thread continues to run and potentially execute other code in the meantime. The code you write looks and behaves as if it was blocking, which some people have labelled "blocking-style code", but from a user perspective, it might as well be blocking.

Thread Thread
maixuanhan profile image
Han Mai • Edited

You can add any more concepts or play with words the way that you like but don’t make it equivalent with the original meaning of the program thread. If it is in blocking state, no way it can go further and execute the other code. Period. If it can, then it is not blocking.

When you call an “await”, it will make a callback for that whole “async” function (the one that contains the calling await), starting from that “await” point. And then it will execute the next statement of the outer function in the call stack. That is what “users” should understand about the syntactic sugar. Not like what you said which is very vague.

Thread Thread
darkwiiplayer profile image
DarkWiiPlayer

I don't think it's "vague": I simply look at the next line of code after a subroutine call, and ask "Will this run immediately, or wait until the subroutine finishes?" and in the case of await, the latter is the case. In other words, the current thread of execution is definitely blocked, while the OS-level thread is not.

Thread Thread
valeriavg profile image
Valeria

This is not the case for JavaScript. There are no "hidden" threads in JS. There is a call stack and an event loop. Sync operations for JavaScript is an edge case of async operation, not the other way around.
You can read more detailed explanation here: developer.mozilla.org/en-US/docs/W...
A short quote from it:

A very interesting property of the event loop model is that JavaScript, unlike a lot of other languages, never blocks. Handling I/O is typically performed via events and callbacks, so when the application is waiting for an IndexedDB query to return or an XHR request to return, it can still process other things like user input.

Thread Thread
darkwiiplayer profile image
DarkWiiPlayer

I couldn't disagree more; JavaScript is an inherently synchronous language. Almost every language with some basic dynamic features can implement an event loop; even C can do this using function pointers. One might argue (on very thin ice), that the runtime, that is, the browser, is inherently asynchronous.

As for the "hidden" threads in JS, that's also not quite true. While there are no clearly distinct threads in the traditional sense, there can definitely be several threads of execution running concurrently and being non-preemptively scheduled. This is why I call them threads of execution, to make clear I'm not talking about the narrower concept of a thread.

And once again, yes, those threads of execution will be paused until an async function finishes. They are blocked.

And if your point is that OS threads are "special", and the concept of "blocking" only makes sense for them, please consider that they are fundamentally the same as an async javascript function: they run synchronously, and when they make blocking system call, the kernel will put them on hold and let other threads have some CPU time until the blocking operation has finished, at which point it will allow that program to resume its execution. The only difference is that OS threads are preempted, but that doesn't make a big difference in this situation.

Collapse
arikaturika profile image
Arika O Author

Thank you for pointing this out, I made the necessary correction.

Collapse
arikaturika profile image
Arika O Author • Edited

Javascript is synchronous at its core. The fact that it can handle async operations throught the use of callbacks, promises or sync/ await doesn't cancel that and they will all be discussed in the next articles. Since the possibilty of being mislead exists (your comment being proof of that), I will try to edit the article in such a way to highlight the async capabilities too :). Thx for the input.

Collapse
darkwiiplayer profile image
DarkWiiPlayer

Javascript doesn't really have any special ability to handle async operations; in fact, you can write the same kind of "async" code in assembly, and that's how interrupt-handlers have worked since before operating-systems were even a thing.

What made javascript popular in the first place was its monopoly on web applications. You have to learn javascript if you want to do anything interactive on the web.

Heavy synchronous operation will block the whole thread in any language

Absolutely not. Any language with scheduled light-threads easily avoids this problem, and even scheduled coroutines don't face the same issue if they deliberately yield every so often.

All in all, I don't see how this article is misleading. If Javascript is inherently synchronous, then so are C, Pascal and Assembly.

Collapse
aquadry profile image
aquadry

Lot of wrong explaining, sorry.
The strong of javascript among other is callback function and it's asynchronous pattern.

Collapse
peerreynders profile image
peerreynders

JavaScript follows a synchronous execution model.

Granted this could be easily misunderstood by a neophyte - so lets be more specific:

JavaScript code is synchronous as long as it doesn't relinquish the thread back to the event loop.

Example:

const log = console.log;

// https://toolshed.com/articles/1998-07-01-TellDontAsk.html
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
function tell(id, utask) {
  // log synchronously
  log(id);

  // schedule microtask
  queueMicrotask(utask);
}

function demo() {
  tell(1, () => log(5));
  tell(2, () => log(6));
  tell(3, () => log(7));
  tell(4, () => log(8));
}

demo();
log('demo exited');
Enter fullscreen mode Exit fullscreen mode

Output:

1
2
3
4
demo exited
5
6
7
8
Enter fullscreen mode Exit fullscreen mode

For plain functions like demo() code execution is synchronous. Any code on the JS stack executes synchronously. It doesn't matter how many microtasks and tasks are being scheduled - they'll just sit there while there is still code on the JS stack. This means that nothing on the microtask and task queues can interfere with (or interrupt) the execution of the code on the JS stack. The event loop only gets the thread back once the JS stack is empty and then it can hand over control to the next microtask (and later tasks).

JavaScript code is only considered cooperative if it lets the JS stack run to empty in a reasonable amount of time.

Contrast that with async functions - they only create the "illusion of synchronous execution" which can be deceptive.

const log = console.log;

async function tellAsync(id, utask) {
  log(id);
  queueMicrotask(() => queueMicrotask(utask));
}

async function demoAsync() {
  await tellAsync(1, () => log(3));
  await tellAsync(2, () => log(5));
  await tellAsync(4, () => log(7));
  await tellAsync(6, () => log(8));
}

demoAsync();
log('demoAsync exited');
Enter fullscreen mode Exit fullscreen mode

Output:

1
demoAsync exited
2
3
4
5
6
7
8
Enter fullscreen mode Exit fullscreen mode

Many people prefer the async/await syntax to raw promises because they find it "easier to reason about". That "ease" is based on the fact that it appears synchronous (and therefore familiar) - despite all the async and await warning signs.

However the guarantees are entirely different:

  • In the plain, synchronous demo() function the order of operations 1, 2, 3, 4 is preserved and no code on the microtask and task queue has any chance of getting in between those operations.
  • In demoAsync() the order of operations 1, 2, 4, 6 is preserved as well but code from the microtask and task queue can run in between operations (here 3 and 5) potentially changing some shared mutable values (making async/await less easy to reason about).

Because synchronous code is easier to reason about it's essential to be aware where the boundaries around the truly synchronous chunks are (which can be difficult with code that uses plain callbacks which can be used synchronously or asynchronously) and where chunks of code are merely sequenced while appearing synchronous.

Identifying those boundaries is easier when one knows how the event loop works and what code runs together on the JS stack and what code is scheduled to run later via the microtask and task queues.

Collapse
darkwiiplayer profile image
DarkWiiPlayer • Edited

To be honest, I find await quite obvious as a sign that the execution of the code will pause then and there, and other things might happen before it resumes.

It's easy to miss visually, being just one keyword, but it's quite explicit in what it means.


EDIT: Other than that, really nice comment though :D

Thread Thread
peerreynders profile image
peerreynders

To be honest, I find await quite obvious

I don't disagree - it's just that plain functions are a nice package of "run to completion" script (that may be scheduling other code for later execution) - async functions are completely different beasts (What color is your function? vs. Red & blue functions are actually a good thing).

And while async/await may simplify code with a sequential execution order, I'm convinced that understanding promises and how they relate to async/await is key when writing code with concurrent (not parallel) execution order.

Collapse
darkwiiplayer profile image
DarkWiiPlayer

Thinking CPS is a good way to write asynchronous code has to be a form of Stockholm syndrome.

No, callbacks are not a strength of javascript. They're neither a new invention (heck, they've been around since longer than programming languages), nor are they in any way a good way of writing async code.

Collapse
prakashmardi profile image
Prakash Mardi

Have you worket on React js

Collapse
arikaturika profile image
Arika O Author

Yes, I use it at work every day.

Collapse
prakashmardi profile image
Prakash Mardi

Ok, will you work with us?

Collapse
richwandell profile image
Rich Wandell

Safari js engine is called javascriptcore not webkit

Collapse
arikaturika profile image
Arika O Author

True, thx.

Collapse
prosenjit98 profile image
prosenjit98

when will come next part?

Collapse
arikaturika profile image
Arika O Author

Today or tomorrow.

Collapse
aquadry profile image
aquadry

I advise you to rewrite the article, for novices it will inducted them on error while looking to understand the JS foundation functioning.