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.
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.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.