Frontend Development 14 min read

Understanding Asynchronous Programming in JavaScript: Event Loop, Tasks, and Promise Implementation

The article explains JavaScript’s single‑threaded nature and how asynchronous programming—using callbacks, timers, Ajax, and Promises—relies on the event loop to manage macro‑tasks and micro‑tasks, illustrates execution order, warns against callback hell, and provides a custom Promise implementation.

37 Interactive Technology Team
37 Interactive Technology Team
37 Interactive Technology Team
Understanding Asynchronous Programming in JavaScript: Event Loop, Tasks, and Promise Implementation

When learning JavaScript we discover that its execution environment is single‑threaded, meaning only one task can run at a time. If multiple tasks are queued, they are processed sequentially, and a long‑running script (e.g., an infinite loop) can freeze the whole page.

To avoid the problems caused by the single‑threaded model, front‑end developers use asynchronous programming. This article explains the runtime mechanism of browser asynchronous code, covering common async tasks, the event loop, macro‑tasks, micro‑tasks, and the implementation of Promise and a custom promise.

1. Asynchronous Tasks

Typical async tasks in the front‑end include:

Callback functions

Event bindings

Timers ( setTimeout , setInterval )

Ajax requests

Promises

Example: a simple Promise whose executor runs synchronously while the then handler runs asynchronously.

new Promise((resolve, reject) => {
  console.log(1);
  resolve();
}).then(() => {
  console.log(2);
});
console.log(3);

The console output is 1 3 2 , showing that the executor runs immediately and the then callback is deferred.

Another example using async/await (syntactic sugar over generators):

async function async1() {
  console.log(1);
  await 10;
  console.log(2);
}
async1();
console.log(3);

The output order is 1 3 2 , confirming that code after await is executed asynchronously.

2. Event Loop

The event loop coordinates the execution of synchronous code, macro‑tasks, and micro‑tasks. The process is:

Start executing a task.

Synchronous code runs on the Call Stack. Asynchronous operations register callbacks in the Event Table, which later move to the Event Queue.

When the Call Stack is empty, the Event Loop pulls the next task from the Event Queue into the Call Stack.

After a macro‑task finishes, all pending micro‑tasks are executed before the next macro‑task.

Example with multiple setTimeout calls and a heavy for loop:

setTimeout(() => { console.log(1); }, 50);
setTimeout(() => { console.log(2); }, 0);
console.time('for takes time:');
for (let i = 0; i < 1000000; i++) {}
console.timeEnd('for takes time:');
setTimeout(() => { console.log(3); }, 20);
console.log(4);

Depending on the time spent in the loop, the order of 1 , 2 , and 3 changes, illustrating the priority of tasks based on their scheduled delay.

3. Macro‑Task vs. Micro‑Task

JavaScript distinguishes two kinds of asynchronous jobs:

Macro‑tasks : script , setTimeout , setInterval , I/O, UI events, postMessage , MessageChannel , setImmediate (Node.js).

Micro‑tasks : Promise.then/catch/finally , async/await , Object.observe , MutationObserver , process.nextTick (Node.js).

The execution order is: synchronous code → micro‑tasks → macro‑tasks.

4. Callback Hell

When many asynchronous operations are nested, code becomes hard to read and maintain. The following snippet shows a typical “callback hell” scenario for three sequential Ajax calls:

// Get system data
_self.ajaxFn({
  url: url1,
  success: (res) => {
    // Get form category
    _self.ajaxFn({
      url: url2,
      success: (res) => {
        // Get form data
        _self.ajaxFn({
          url: url3,
          success: (res) => {
            // handle business logic
          }
        });
      }
    });
  }
});

5. Promise Principles and Custom Implementation

Promises provide a cleaner way to handle asynchronous flows. The article presents a hand‑crafted implementation covering:

State transitions ( pending → resolved / rejected ) via resolve , reject , and thrown exceptions.

Storing then / catch callbacks and executing them as micro‑tasks.

Chaining: then returns a new Promise whose outcome depends on the callback’s return value.

Key code fragments:

// Custom Promise constructor
function Promise(executor) {
  const _self = this;
  _self.status = 'pending';
  _self.data = undefined;
  _self.callbacks = [];
  function resolve(value) { /* ... */ }
  function reject(reason) { /* ... */ }
  try { executor(resolve, reject); } catch (e) { reject(e); }
}

// then implementation
Promise.prototype.then = function(onResolved, onRejected) {
  const _self = this;
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r; };
  return new Promise((resolve, reject) => {
    function handle(callback) {
      try {
        const result = callback(_self.data);
        if (result instanceof Promise) {
          result.then(resolve, reject);
        } else {
          resolve(result);
        }
      } catch (e) { reject(e); }
    }
    if (_self.status === 'resolved') {
      nextTick(() => handle(onResolved));
    } else if (_self.status === 'rejected') {
      nextTick(() => handle(onRejected));
    } else {
      _self.callbacks.push({
        onResolved() { handle(onResolved); },
        onRejected() { handle(onRejected); }
      });
    }
  });
};

// catch simply forwards to then
Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
};

The implementation uses nextTick (based on MutationObserver or setTimeout ) to schedule micro‑tasks.

Conclusion

The article reviews the JavaScript asynchronous execution model, explains the event loop, distinguishes macro‑tasks and micro‑tasks, demonstrates why callback nesting is problematic, and provides a practical custom Promise implementation.

frontendjavascriptasynchronous programmingevent-loopmacrotaskMicrotaskPromise
37 Interactive Technology Team
Written by

37 Interactive Technology Team

37 Interactive Technology Center

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.