Skip to content

Instantly share code, notes, and snippets.

@shalvah
Last active September 3, 2020 07:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shalvah/0c21ff0102aabb683d00ddaa91f63e82 to your computer and use it in GitHub Desktop.
Save shalvah/0c21ff0102aabb683d00ddaa91f63e82 to your computer and use it in GitHub Desktop.
Simplified implementation of a promise, inspired by https://exploringjs.com/deep-js/ch_implementing-promises.html
/**
* Simple promise implementation. Doesn't have all the features of the actual Promise API, + assumes you know what you're doing.
*/
class ToyPromise {
constructor(cb) {
this.state = 'pending';
this._promiseResult = undefined;
this._fulfillmentCallbacks = [];
this._rejectionCallbacks = [];
// cb is the (resolve, reject) => {} callback the user passes when creating a Promise.
// The resolve and reject parameters are actually our internal _resolve and _reject methods, ha!
cb(this._resolve.bind(this), this._reject.bind(this));
}
// Helper method to queue a list of functions for execution asynchronously
_queueTasks(...tasks) {
tasks.forEach(t => setTimeout(t, 0));
}
then(onFulfilled) {
// First rule: .then() returns a new Promise
return new ToyPromise((res, rej) => {
const fulfillmentTask = () => {
try {
const result = onFulfilled(this._promiseResult);
// The new Promise should resolve when the then() callback is done
res(result);
} catch (e) {
// The new Promise should reject if an error was thrown in the then() callback
rej(e);
}
};
// Back to the original Promise...
if (this.state === 'fulfilled') {
// If the Promise is already done when .then(cb) is called, execute cb immediately (asynchronously!)
this._queueTasks(fulfillmentTask);
} else if (this.state === 'pending') {
// Otherwise, store the callback for later.
// Note that we're pushing onto an array rather than overwriting a value,
// so that you can call .then() multiple times on the same Promise if you like (i.e. no chaining)
this._fulfillmentCallbacks.push(fulfillmentTask);
}
});
}
catch(onRejected) {
return new ToyPromise((res, rej) => {
const rejectionTask = () => {
try {
const result = onRejected(this._promiseResult);
res(result);
} catch (e) {
rej(e);
}
}
if (this.state === 'rejected') {
this._queueTasks(rejectionTask);
} else if (this.state === 'pending') {
this._rejectionCallbacks.push(rejectionTask);
}
});
}
// The function that actually resolves the Promise.
// When you write `new Promise((resolve, reject)) => resolve(value))`, THIS is the resolve() you're calling
_resolve(value) {
if (this.state !== 'pending') return this;
this.state = 'fulfilled';
this._promiseResult = value;
// Execute all the registered callbacks and empty the list
this._queueTasks(...this._fulfillmentCallbacks);
this._fulfillmentCallbacks = [];
this._rejectionCallbacks = [];
}
_reject(value) {
if (this.state !== 'pending') return this;
this.state = 'rejected';
this._promiseResult = value;
this._queueTasks(...this._rejectionCallbacks);
this._fulfillmentCallbacks = [];
this._rejectionCallbacks = [];
}
}
// Usage
const prom = new ToyPromise((res, rej) => {
setTimeout(() => res(6), 2000);
});
prom.then(v => {
console.log("Expected 6; resolved with " + v);
return 10;
}); // Note that the "10" here is discarded, since we didn't chain on the new Promise returned
prom.then(v => {
console.log("Expected 6; resolved with " + v);
return 11;
}).then(v => {
console.log("Expected 11; resolved with " + v);
throw new Error('catch me if you can');
}).catch(e => {
console.log("Expected 'Error: catch me if you can'; rejected with " + e);
return "Final value";
}).then(console.log);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment