Asynchronous JavaScript
JavaScript executes one thread at a time. A synchronous software will block the whole execution of a lengthy job if it comes across it, rendering the application unavailable. Waiting for these lengthy operations can be expressed using asynchronous programming without causing the program to freeze.
The fundamental tenet of asynchronicity is that an action is started “now,” but it is completed and any follow-up activities take place “later.” Callbacks are the most straightforward method for handling this “later” portion. When you send a function to another function, that function invokes (or “calls back”) your function once the asynchronous process is finished. This is known as a callback.
SetTimeout() is a standard callback-based asynchronous method. It lets you delay a function’s execution.
Consider this code:
console.log(“Hello”);
setTimeout(() => {
console.log(“File Downloaded!”);
}, 3000);
console.log(“World”);
You would anticipate seeing “Hello” followed by “File Downloaded!” and “World” after three seconds of waiting if this code executed synchronously. However, the application doesn’t block because setTimeout is asynchronous. As soon as the setTimeout task is outsourced (for example, to a browser’s Web API), the primary program resumes running. “Hello” is the output, followed by “World” and “File Downloaded!” following a 3-second pause. The callback function will still wait for the current script execution to conclude before running, even if the delay is set to zero (setTimeout(() => {… }, 0)).
Callbacks are essential, but they can result in deeply nested code a phenomenon known as “callback hell”when used to chain several asynchronous tasks. Because of this structure, it is challenging to think logically about the execution flow.
Promises were created to solve the drawbacks of callbacks. The final outcome of an asynchronous operation, which might not be available at this time, is represented by an object called a promise. There are three possible states for promises: rejected, fulfilled (resolved), or pending. Using the.then() method to handle the fulfilled value and the.catch() method to manage errors (rejections), you can add handlers to a promise. Cleanup code that executes whether the Promise was accepted or rejected might utilize the.finally() function. Chaining promises facilitates the expression of asynchronous operation sequences.
Here’s an example using a Promise:
function checker(val) {
return new Promise((resolve, reject) => {
if (val > 5) {
resolve(“Ready”);
} else {
reject(new Error(“Oh no”));
}
});
}
checker(5)
.then((data) => {console.log(data); }) // This won’t run
.catch((err) => {console.error(err); }); // This will catch the error
In ES2017, the async and await keywords were added, building on Promises. A function that implicitly returns a Promise is known as an async function. The await keyword can be used before a Promise inside an async function. The async function’s execution is stopped until the Promise is resolved when await is encountered. The fulfilled value of the promise is the value of the await expression. You can build asynchronous code using this syntax that reads and looks a lot like synchronous code.
Consider the Promise example rewritten with async/await:
function saySomething(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(“something” + x);
}, 2000);
});
}
async function talk(x) { // Declared as async
const words = await saySomething(x); // await pauses execution until the Promise resolves
console.log(words);
}
talk(2);
talk(4);
talk(8);
// Although called in sequence, the awaits cause them to log after 2 seconds
JavaScript uses an event loop to handle asynchronicity internally. The event loop manages external duties (such as timers, network requests, and user events) despite JavaScript’s single-threaded nature. A callback (or Promise resolution handler, etc.) is added to a callback queue or microtask queue upon the completion of an asynchronous task. The main call stack, which is where synchronous code runs, is constantly checked for emptyness by the event loop. The event loop processes jobs from the queues, carrying them out one at a time, only when the call stack is empty. By preventing the main thread from being blocked by lengthy asynchronous activities, this method keeps the browser or Node.js environment responsive.
In conclusion, JavaScript asynchronous programming is crucial for creating responsive apps, especially in settings with a single thread. With the help of the underlying event loop architecture, it has progressed from callbacks to Promises and finally to the more understandable async/await syntax.