Mastering JavaScript Promises: Event Loop, Tasks, and Advanced Patterns

This article explains JavaScript’s event mechanism, distinguishes macro‑ and micro‑tasks, and provides a comprehensive guide to using Promises—including their states, API methods like then, catch, finally, and utilities such as all, race, with practical code examples and common pitfalls.

MaoDou Frontend Team
MaoDou Frontend Team
MaoDou Frontend Team
Mastering JavaScript Promises: Event Loop, Tasks, and Advanced Patterns

ES6's Promise object is a crucial point in asynchronous programming; this note shares a study guide, beginning with an overview of the JavaScript event mechanism and related asynchronous operations.

JS Event Mechanism

Before discussing the JS event mechanism, note that browsers run multiple processes. The JS engine lives in the rendering process and operates on a single thread, executing one task at a time. When a task takes too long, the UI can freeze. Asynchronous programming solves this single‑threaded bottleneck.

When the JS engine encounters an asynchronous event, it does not wait for the result; it suspends the event and continues executing other tasks on the call stack. Once the asynchronous event completes, its callback is placed into a separate queue—the event queue. The callback runs only after the current call stack is empty and the main thread is idle.

Macro Tasks & Micro Tasks

The event loop is a macro description; asynchronous tasks have different priorities and are divided into macro‑tasks and micro‑tasks.

Execution mechanism: 1) Execute a macro‑task (if none, fetch from the event queue). 2) If a micro‑task appears, add it to the micro‑task queue. 3) After the macro‑task finishes, run all micro‑tasks in order. 4) After the macro‑task completes, the GUI thread renders. 5) JS resumes and starts the next macro‑task.

Classic interview question:

setTimeout(() => { console.log(4); }, 0);
new Promise(resolve => {
    console.log(1);
    resolve();
    console.log(2);
}).then(() => { console.log(5); });
console.log(3);
// Result: 1 2 3 5 4

Promise is essentially an asynchronous micro‑task; let’s start the Promise journey.

Promise

Introduction

Promise is a solution for asynchronous programming that is more reasonable and powerful than traditional callbacks or events. It acts as a container holding the result of a future asynchronous operation. As an object, it returns a new Promise after each operation, supporting chaining and allowing asynchronous code to be expressed in a synchronous style.

Key Characteristics

Promise objects have three immutable states: pending (in progress), fulfilled (success), and rejected (failure). Only the outcome of the asynchronous operation determines the state.

Once a Promise’s state changes, it never changes again; the result is always available. The state transition is either pending → fulfilled or pending → rejected.

The state, once set, cannot be altered, and the result can be retrieved at any time.

Drawbacks

1. A Promise cannot be cancelled; it starts executing immediately upon creation.

2. Errors thrown inside a Promise are not automatically propagated if no callback is attached.

3. While pending, there is no way to know the progress of the operation.

Promise API

From the diagram, Promise is both an object and a constructor.

Basic Usage

resolve / reject

let promise = new Promise((resolve, reject) => {
    if (/** operation succeeds */) {
        resolve(success);
    } else {
        reject(error);
    }
});

The Promise constructor receives a function with resolve and reject parameters. Calling resolve fulfills the Promise; calling reject rejects it.

let p1 = new Promise((resolve, reject) => {
    reject('error');
});
let p2 = new Promise((resolve, reject) => {
    resolve(p1);
});
p2.then(s => console.log(s)).catch(e => console.log(e));

then()

After a Promise is settled, then registers callbacks for the fulfilled and rejected states.

let promise = new Promise();
promise.then(success => {
    // equivalent to resolve(success)
}, error => {
    // handle rejection
});
then(onFulfilled, onRejected)

returns a new Promise, enabling chainable calls and avoiding nested callbacks.

function createPromise(p, state) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (state === 0) {
                reject(`error, ${p}`);
            } else {
                resolve(`success, ${p}`);
            }
        }, 0);
    });
}
createPromise('p1', 1).then(s => {
    console.log('111', s);
    return createPromise('p2', 2);
}).then(s => {
    console.log('222', s);
    return createPromise('p3', 3);
}).then(s => {
    console.log('333', s);
});
// 111 success, p1
// 222 success, p2
// 333 success, p3

catch() catch is an alias for .then(null, onRejected), handling errors from either the fulfilled or rejected side.

new Promise().then(success => {
    // ...
}).catch(error => {
    // handle error
});

finally() finally runs regardless of the Promise’s final state and does not receive any arguments.

createPromise('p1', 1).then(s => {
    console.log('111', s);
}).catch(e => {
    console.log('222', e);
}).finally(() => {
    console.log('finally');
});
// 111 success, p1
// finally

all() Promise.all takes an array of Promises and resolves when all have fulfilled; if any reject, the returned Promise rejects with the first rejection reason.

Promise.all([createPromise('p1', 1), createPromise('p2', 1)])
    .then(r => console.log(r)); // ["success, p1", "success, p2"]
Promise.all([createPromise('p1', 1), createPromise('p2', 0)])
    .then(r => console.log(r))
    .catch(e => console.log(e)); // error, p2

If a Promise in all has its own catch, the rejection is handled there and all may still resolve.

let p2 = createPromise('p2', 0).catch(e => console.log('p2-catch', e));
Promise.all([createPromise('p1', 1), p2])
    .then(r => console.log(r))
    .catch(e => console.log(e));
// p2-catch error, p2
// ["success, p1", undefined]

race() Promise.race resolves or rejects as soon as the first Promise in the iterable settles.

Promise.race([createPromise('p1', 1), createPromise('p2', 0)])
    .then(r => console.log(r))
    .catch(e => console.log(e));
// success, p1
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptasyncEvent LoopPromise
MaoDou Frontend Team
Written by

MaoDou Frontend Team

Open-source, innovative, collaborative, win‑win – sharing frontend tech and shaping its future.

0 followers
Reader feedback

How this landed with the community

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.