JavaScript is a single-threaded programming language, and the language can perform operations in either a synchronous or asynchronous manner. By default the codes in JavaScript executes synchronously. But we can achieve the async nature in JS using Promises and Callbacks. In this blog post let’s explore the fundamental differences between synchronous and asynchronous operations using code examples.
Synchronous Operations
In synchronous operations, code is executed sequentially, one statement after another. Each statement must complete before the next one begins. Let’s consider the following example:
console.log('Welcome');
let x = 0;
for(i=0; i<=1000000000; i++) {
x = i;
}
console.log(x);
console.log('Goodbye Code');
#Output
Welcome
1000000000
Goodbye Code
In this example, the ‘Welcome’ message is logged, then a time-consuming for loop runs, and finally, the loop completes, and ‘Goodbye Code’ is logged. The code executes in a predictable and linear fashion.
Asynchronous Operations
Asynchronous operations, on the other hand, allow code to run independently of the main program flow. JavaScript achieves this using features like callbacks, promises, and async/await.
Promises for Asynchronous Execution
Here’s an example using promises:
console.log('Welcome');
let f = () => {
return new Promise((resolve, reject) => {
let x = 0;
for(i=0; i<=100000000; i++) {
x = i;
}
if(x > 0)
resolve(x);
else
reject("Got an error");
});
}
f().then((x) => {
console.log('Goodbye Promise: ' + x);
}).catch((err) => {
console.log('Error in Promise: ' + err);
});
console.log('Goodbye Code');
#Output
Welcome
Goodbye Code
Goodbye Promise: 100000000
In this asynchronous example, the ‘Welcome’ message is logged, the asynchronous function f()
is called, and immediately the program proceeds to the ‘Goodbye Code’ message without waiting for the function to complete. The promise is resolved later, and the corresponding then
or catch
block is executed.
Callback Functions with Asynchronous Behavior
Callback functions can also exhibit asynchronous behavior depending on how they are implemented. While the first example may seem synchronous, it’s essential to note that callbacks themselves don’t guarantee asynchrony because it’s not asynchronous by nature. However, of course, we can use callbacks for asynchronous operations. Here’s an example:
console.log('Welcome');
function myCallbackFunction(args, callback) {
let x = 0;
for(i = 0; i <= 1000000000; i++) {
x = i;
}
// Simulating an asynchronous operation using setTimeout
setTimeout(() => {
if (x > 0)
return callback(null, x);
else
return callback('Got an error!', null);
}, 0);
}
myCallbackFunction('Hello World!', function(err, data) {
if (err) console.log('Error in Callback: ' + err);
console.log('Goodbye Callback: ' + data);
});
console.log('Goodbye Code');
#Output
Welcome
Goodbye Code
Goodbye Callback: 1000000000
In this adjusted example, I’ve introduced setTimeout
to simulate an asynchronous operation within the callback function. Now, the program will log “Welcome,” initiate an asynchronous operation inside the callback, proceed to the next line console.log('Goodbye Code')
, and eventually execute the callback when the asynchronous task is complete. This more accurately reflects the asynchronous nature of callback functions when dealing with real-world asynchronous operations.
Conclusion
Understanding the difference between synchronous and asynchronous operations is crucial for writing efficient and responsive JavaScript code. Synchronous operations follow a sequential order, blocking execution until completion, while asynchronous operations allow concurrent execution, improving the overall responsiveness of applications. Callback functions, often associated with asynchrony, gain their asynchronous nature when handling asynchronous tasks within their implementations. Whether you’re dealing with Promises or callbacks, mastering both synchronous and asynchronous patterns is essential for effective JavaScript development.