Frontend Development 10 min read

In‑Depth Implementation of JavaScript Promise: Prototype Methods, Error Handling, and Finally

This article walks through a step‑by‑step construction of a fully‑featured JavaScript Promise, detailing prototype methods, chainable then, reject handling, catch alias, and a standards‑compliant finally implementation, while illustrating each stage with code snippets, flowcharts, and animated visualizations.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
In‑Depth Implementation of JavaScript Promise: Prototype Methods, Error Handling, and Finally

Promise is an asynchronous programming solution that was first proposed by the community and later standardized in ES6. This article series explains the inner workings of Promise by gradually implementing it, using flowcharts, examples, and animations to achieve a deep understanding.

The series consists of four chapters:

Basic implementation of Promise

Promise chain calls

Prototype method implementation

Static method implementation

Prototype implementation

class Promise {
    callbacks = [];
    state = 'pending'; // increase state
    value = null; // store result
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        return new Promise(resolve => {
            this._handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    }
    _handle(callback) {
        if (this.state === 'pending') {
            this.callbacks.push(callback);
            return;
        }
        // if then receives nothing
        if (!callback.onFulfilled) {
            callback.resolve(this.value);
            return;
        }
        var ret = callback.onFulfilled(this.value);
        callback.resolve(ret);
    }
    _resolve(value) {
        if (value && (typeof value === 'object' || typeof value === 'function')) {
            var then = value.then;
            if (typeof then === 'function') {
                then.call(value, this._resolve.bind(this));
                return;
            }
        }
        this.state = 'fulfilled'; // change state
        this.value = value; // store result
        this.callbacks.forEach(callback => this._handle(callback));
    }
}

Demonstrating rejection

/**
 * Simulate an asynchronous request that may fail
 */
const mockAjax = (url, s, callback) => {
  setTimeout(() => {
    callback(url + '异步请求耗时' + s + '秒', '出错了!');
  }, 1000 * s);
};

// demo reject
new Promise((resolve, reject) => {
    mockAjax('getUserId', 1, function (result, error) {
        if (error) {
            reject(error);
        } else {
            resolve(result);
        }
    });
}).then(result => {
    console.log(result);
}, error => {
    console.log(error);
});

Full implementation with reject support

class Promise {
    callbacks = [];
    state = 'pending'; // increase state
    value = null; // store result
    constructor(fn) {
        fn(this._resolve.bind(this), this._reject.bind(this));
    }
    then(onFulfilled, onRejected) {
        return new Promise((resolve, reject) => {
            this._handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    }
    _handle(callback) {
        if (this.state === 'pending') {
            this.callbacks.push(callback);
            return;
        }
        let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
        if (!cb) {
            cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
            cb(this.value);
            return;
        }
        let ret = cb(this.value);
        cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
        cb(ret);
    }
    _resolve(value) {
        if (value && (typeof value === 'object' || typeof value === 'function')) {
            var then = value.then;
            if (typeof then === 'function') {
                then.call(value, this._resolve.bind(this), this._reject.bind(this));
                return;
            }
        }
        this.state = 'fulfilled'; // change state
        this.value = value; // store result
        this.callbacks.forEach(callback => this._handle(callback));
    }
    _reject(error) {
        this.state = 'rejected';
        this.value = error;
        this.callbacks.forEach(callback => this._handle(callback));
    }
}

Error handling inside callbacks

_handle(callback) {
    if (this.state === 'pending') {
        this.callbacks.push(callback);
        return;
    }
    let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
    if (!cb) {
        cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
        cb(this.value);
        return;
    }
    let ret;
    try {
        ret = cb(this.value);
        cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
    } catch (error) {
        ret = error;
        cb = callback.reject;
    } finally {
        cb(ret);
    }
}

Catch method (alias for then(null, onRejected))

then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
        this._handle({
            onFulfilled: onFulfilled || null,
            onRejected: onRejected || null,
            resolve: resolve,
            reject: reject
        });
    });
}
catch(onError) {
    return this.then(null, onError);
}

Finally method implementation

finally(onDone) {
    if (typeof onDone !== 'function') return this.then();
    let Promise = this.constructor;
    return this.then(
        value => Promise.resolve(onDone()).then(() => value),
        reason => Promise.resolve(onDone()).then(() => { throw reason; })
    );
}

A demonstration of finally shows that the callback runs regardless of the Promise’s fulfillment or rejection state, and that the implementation must avoid passing the result to the callback and must handle cases where the callback returns a Promise.

The article also provides links to the source code demos hosted on repl.it and visual animations illustrating the flow of Promise states.

frontendJavaScriptasynchronouserror handlingPromisefinally
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

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.