JavaScript: The Magic Behind the Scenes! Learn How Your Code Comes to Life.

JavaScript: The Magic Behind the Scenes! Learn How Your Code Comes to Life.

JavaScript is a unique programming language in that it operates in a single-threaded environment, meaning that only one operation can occur at a time. This is because the JavaScript engine can only process one statement at a time, which limits the amount of work that can be performed simultaneously.

The behavior of JavaScript where only one operation can occur at a time due to its single-threaded environment is known as synchronous behavior.

Single-threaded programming languages, such as JavaScript, can make coding simpler by eliminating concurrency issues. However, this also means that long operations, like network access, can block the main thread, leading to unresponsive web pages.

To address this issue, asynchronous JavaScript was introduced. Asynchronous programming enables developers to execute long-running operations without blocking the main thread, ensuring that the web page remains responsive.

For instance, when requesting an API, the server may take some time to process the request, causing the main thread to block. With asynchronous programming, JavaScript can initiate the request and continue executing other code while waiting for a response from the server. Once the response is received, the appropriate code is executed to handle the response.

To truly unlock the power of asynchronous programming in JavaScript, we must first gain a thorough understanding of how synchronous code operates within the JavaScript engine.

Without any more delay, let's begin our journey and Explore the world of JavaScript programming.

How Does Synchronous JavaScript Work?

To understand how JavaScript Synchronous code executes, we need to know about execution context and call stack, which are also called execution stack.

Execution Context :

An execution context is an abstract concept that refers to the environment in which JavaScript code is evaluated and executed. Every time code is run in JavaScript, it occurs within an execution context. Functions have their own execution context, and the global code runs within the global execution context.

Call Stack :

The call stack in JavaScript is a Last-In-First-Out (LIFO) structure that stores all execution contexts generated during code execution. Because JavaScript is a single-threaded language, it has only one call stack. Items can only be added or removed from the top of the stack.

Now, let's take a closer look at our code snippet and see how it executes within the JavaScript engine.

const secondFun = () => {
  console.log('second Function!');
}
const firstFun = () => {
  console.log('first Function!');
  secondFun();
  console.log(' first & second End');
}
firstFun();

Here's how the code above executes using the call stack:

  1. When the program starts, the global execution context is created and pushed onto the call stack.

  2. The global execution context initializes and the firstFun function is defined in memory.

  3. The firstFun function is called and a new execution context for firstFun is created and pushed onto the call stack.

  4. Inside the firstFun execution context, the console.log statement is executed, printing "This is first Function!" to the console.

  5. The secondFun function is called, creating a new execution context for secondFun that is pushed onto the top of the call stack.

  6. Inside the secondFun execution context, the console.log statement is executed, printing "This is Second Function!" to the console.

  7. The secondFun execution context is popped off the call stack.

  8. Execution returns to the firstFun execution context and the console.log statement is executed, printing "The is first & Second End" to the console.

  9. The firstFun execution context is popped off the call stack.

  10. Execution returns to the global execution context and the program ends.

How Does Asynchronous JavaScript Work?

Certainly! Now that we have a foundational understanding of the call stack and synchronous JavaScript, let's dive deeper into asynchronous JavaScript.

const calculateSum = (num1, num2) => {
/**

doing some calculations
**/
console.log('Sum calculated');
return num1 + num2;
}
const fetchData = (url) => {
/**

fetching data from API
**/
return fetchedData;
}
const displayResult = (result) => {
console.log(Result: ${result});
}

const num1 = 5;
const num2 = 10;

const sum = calculateSum(num1, num2);
const data = fetchData('www.someapi.com');
const result = sum * data;
displayResult(result);

In this example, we have a function calculateSum() which takes two numbers and calculates their sum. Then we have a function fetchData() which fetches some data from an API. Finally, we have a function displayResult() which simply displays the result on the console.

Similar to the previous example, the execution of calculateSum() and fetchData() functions may take some time depending on the size of the data or the complexity of the calculation.

Once both these functions complete, the result is calculated by multiplying the sum and fetched data, and then displayed on the console using the displayResult() function.

However, this approach blocks the main thread until all the functions complete their execution. This means we can't perform any other operation while these functions are executing.

So, what's the solution?

One way to solve this problem is to use asynchronous programming techniques such as callbacks, promises, or async/await. By using these techniques, we can perform non-blocking operations and make our code more efficient.

One simple solution to make our code non-blocking is to use asynchronous callbacks. For example, we can use the setTimeout() method to simulate a network request, as shown below:

const networkRequest = () => {
  setTimeout(() => {
    console.log('Async Code');
  }, 2000);
};

console.log('Hello World');
networkRequest();

It's important to note that the setTimeout() method is not a part of the JavaScript engine itself, but rather a part of web APIs (in browsers).

To understand how this code is executed, we need to understand a few more concepts, such as the event loop and the callback queue (also known as the task queue, job queue or message queue)

Once the above code is loaded in the browser, the console.log('Hello World') statement is pushed to the call stack and then removed from it once it's finished. Next, the networkRequest() function is called, so it's pushed to the top of the call stack.

Subsequently, the setTimeout() function is called, which is also pushed to the top of the stack. This function takes two arguments: a callback function and a time duration in milliseconds.

After the setTimeout() method is called, it starts a timer of 2 seconds in the web APIs environment. Once this method completes, it's popped off from the stack. Then, the console.log('The End') statement is pushed to the stack, executed, and removed from it after its completion.

Meanwhile, the timer set by the setTimeout() method has expired, and the callback function is pushed to the message queue. However, the callback function is not executed immediately, and this is where the event loop comes into play.

Event Loop:

The Event loop's primary responsibility is to check whether the call stack is empty or not. If the call stack is empty, the Event loop checks the message queue for any pending callbacks that need to be executed.

In the example above, the message queue contains one callback, and the call stack is empty. Therefore, the Event loop pushes the callback to the top of the stack.

Next, the console.log('Async Code') statement is pushed to the stack, executed, and removed from it. Once the callback has finished executing, it's removed from the stack, and the program finally finishes.

Conclusion:

By understanding asynchronous JavaScript and concepts like the call stack, event loop, and message queues, you can write more efficient and non-blocking code. While not essential, it's worthwhile to delve into these concepts to gain a deeper understanding of the JavaScript runtime environment.