Callbacks-and-Higher-Order-Functions :👈 👉:setTimeout() and setInterval()

Asynchronous JavaScript

🚀 What Is Asynchronous JavaScript?

JavaScript is single-threaded, meaning it executes one task at a time. However, asynchronous programming allows JavaScript to handle tasks like API calls, timers, and file reading without blocking the main thread.

Instead of waiting for a task to finish, JavaScript can continue executing other code and handle the result later using:

  • Callbacks
  • Promises
  • Async/Await

This makes your app faster and more responsive.

Asynchronous JavaScript

Asynchronous JavaScript is a non-blocking programming pattern that allows a program to initiate a long-running task and continue running other tasks in the meantime. This is crucial for web applications, as it prevents the user interface from freezing during operations like fetching data from a server or processing large files.

💡 Example: Using setTimeout (Asynchronous)

console.log("Start");

setTimeout(() => {
  console.log("This runs after 2 seconds");
}, 2000);

console.log("End");

🧾 Output:

Start
End
This runs after 2 seconds

The setTimeout function is asynchronous—it schedules the task and moves on, allowing other code to run.

Example: Asynchronous vs. Synchronous

To understand asynchronous behavior, compare it with the default synchronous behavior of JavaScript, where code runs sequentially and blocks the main thread.

Synchronous example

console.log("Start synchronous task.");

function longSyncTask() {
  const start = Date.now();
  // Simulate a long-running process that blocks execution for 3 seconds
  while (Date.now() - start < 3000) {}
  return "Finished long task.";
}

console.log(longSyncTask());
console.log("End synchronous task.");

/*
Output:
Start synchronous task.
Finished long task.
End synchronous task.
*/
  

In this example, "End synchronous task" is blocked and cannot be logged until longSyncTask() is completely finished.

Asynchronous example (using setTimeout)

console.log("Start asynchronous task.");

setTimeout(() => {
  console.log("Asynchronous task completed after 3 seconds.");
}, 3000);

console.log("End asynchronous task.");

/*
Output:
Start asynchronous task.
End asynchronous task.
Asynchronous task completed after 3 seconds.
*/
  

Here, setTimeout() initiates a background task and immediately allows the program to proceed to the next line of code. "End asynchronous task" is logged without delay, while the timeout runs in the background.

✅ Advantages of Asynchronous JavaScript

  • Non-blocking execution: Keeps the UI responsive while waiting for tasks like API responses.
  • Efficient resource usage: Frees up the main thread for other tasks.
  • Better user experience: Especially for web apps that rely on real-time data or animations.
  • Scalable: Ideal for handling multiple I/O operations like network requests or file reads.
  • Improved responsiveness: Your application remains responsive and interactive, providing a better user experience by preventing the UI from freezing during heavy operations.
  • Performance and efficiency: It enables concurrent execution of tasks, such as fetching data from multiple APIs simultaneously, which reduces overall loading time.
  • Better resource management: Tasks involving waiting for I/O (input/output) can run in the background without tying up system resources, which improves scalability.
  • Enhanced user experience: Users can interact with other parts of an application while data is loading in the background, such as with infinite scroll feeds or live chat applications.

❌ Disadvantagesof asynchronous JS

  • Complexity: Harder to understand and debug than synchronous code.
  • Callback Hell: Deeply nested callbacks can make code unreadable.
  • Error handling: Managing errors across async chains can be tricky.
  • Race conditions: Multiple async operations may interfere with each other if not managed properly.
  • Increased code complexity: Asynchronous code can be harder to follow and debug because the flow of execution is not linear.
  • Callback hell: Using nested callbacks to handle a sequence of asynchronous operations can lead to complex, unreadable, and hard-to-maintain code.
  • Difficult error handling: Handling errors in asynchronous code can be more complex than with synchronous code.
  • Potential for race conditions: With multiple asynchronous operations running in parallel, it's possible to encounter race conditions where the timing of events leads to unpredictable results.

📌 When to Use Asynchronous JavaScript

Use it for:

  • Fetching data from APIs
  • Reading files
  • Timers and delays
  • Event handling (e.g., user clicks)
  • Animations or transitions
  • Real-time updates (e.g., chat apps, dashboards)
  • Fetching data: For API calls and other network requests that can take an unpredictable amount of time to complete.
  • Timers and scheduling: For operations like setTimeout and setInterval that need to be executed at a future time.
  • Reading/writing files: In server-side JavaScript (Node.js), for file system operations.
  • Event handlers: For user interactions like button clicks or key presses in the browser.
  • CPU-intensive tasks (with Web Workers): For heavy computations that could block the main thread, such as image processing or complex calculations.

🚫 When Not to Use It

Avoid async code when:

  • Tasks are simple and fast (e.g., basic calculations)
  • You need strict sequential execution
  • You’re writing performance-critical code that doesn’t involve I/O
  • Sequential dependencies: If an operation explicitly depends on the immediate result of a previous operation, it may be simpler to handle it synchronously.
  • Simple, non-blocking tasks: Avoid using asynchronous patterns for simple, quick operations that don't involve waiting.
  • Ultra-high performance, low-level code: In rare cases where absolute maximum performance is needed and every operation must be completed instantly, synchronous code might be chosen.

🧠 Best Practices & Precautions

✅ Best Practices

  • Use Promises or async/await instead of raw callbacks
  • Always handle errors using .catch() or try...catch
  • Keep async logic modular and readable
  • Use named functions instead of anonymous callbacks
  • Avoid deeply nested callbacks—refactor into separate functions
  • Prioritize async/await: For modern asynchronous code, use async/await to write code that is more readable and resembles synchronous logic.
  • Always handle errors: Wrap await calls in try...catch blocks to handle errors gracefully.
  • Use Promise.all() for parallel tasks: Execute multiple independent asynchronous tasks concurrently.
  • Avoid over-nesting: Flatten your code structure for better readability.
  • Be careful with loops: Do not use await inside loops unless sequential execution is required.
  • Utilize modern APIs: Use native APIs like fetch that return promises.
  • Clean up properly: Release resources and unsubscribe from event listeners to prevent memory leaks.

⚠️ Precautions

  • Don’t forget to await async functions
  • Be cautious with shared state across async calls
  • Use tools like Promise.all or Promise.race for managing multiple async tasks
  • Test thoroughly—async bugs can be subtle
Back to Index
Callbacks-and-Higher-Order-Functions :👈 👉:setTimeout() and setInterval()