Skip to content

Instantly share code, notes, and snippets.

@psenger
Last active May 25, 2025 02:03
Show Gist options
  • Select an option

  • Save psenger/b19d967d1ddd1ec228a3a7704791b30a to your computer and use it in GitHub Desktop.

Select an option

Save psenger/b19d967d1ddd1ec228a3a7704791b30a to your computer and use it in GitHub Desktop.
[Design Pattern: Cancelable Promise] #Promise #JavaScript
/**
* This was generated by GPTChat, but it is 100% correct.
* Wrap a Promise that can be canceled.
* @type {CancelablePromise}
*/
module.exports = class CancelablePromise {
/**
* Creates a new CancelablePromise object.
* @class
* @constructor
* @param {Promise} promise - A Promise to wrap.
* @example
* const promise = new Promise((resolve, reject) => {
* setTimeout(() => {
* resolve("Promise completed successfully");
* }, 1000);
* });
*
* const cancelablePromise = new CancelablePromise(promise);
*
* setTimeout(() => {
* cancelablePromise.cancel();
* }, 500);
*
* cancelablePromise
* .then((result) => {
* console.log(result); // Will not be called, since the Promise was canceled
* })
* .catch((error) => {
* console.error(error); // Will log nothing, since the Promise was canceled
* });
*/
constructor( promise ) {
/**
* The wrapped Promise object.
* @type {Promise}
*/
this.__promise = promise
/**
* Whether the Promise has been canceled.
* @type {boolean}
*/
this.__isCanceled = false
}
/**
* Cancels the Promise.
*/
cancel() {
this.__isCanceled = true
}
/**
* Attaches callbacks for the resolution and/or rejection of the Promise, and returns a new CancelablePromise object.
* @param {function} onFulfilled - A function that is called when the Promise is fulfilled. If the Promise is already fulfilled, the function is called immediately.
* @param {function} onRejected - A function that is called when the Promise is rejected. If the Promise is already rejected, the function is called immediately.
* @returns {CancelablePromise} A new CancelablePromise object.
*/
then( onFulfilled, onRejected ) {
const newPromise = this.__promise.then(
( value ) => {
if ( !this.__isCanceled ) {
return onFulfilled( value )
}
},
( reason ) => {
if ( !this.__isCanceled ) {
return onRejected( reason )
}
}
)
return new CancelablePromise( newPromise )
}
/**
* Attaches a callback for only the rejection of the Promise, and returns a new CancelablePromise object.
* @param {function} onRejected - A function that is called when the Promise is rejected. If the Promise is already rejected, the function is called immediately.
* @returns {CancelablePromise} A new CancelablePromise object.
*/
catch( onRejected ) {
const newPromise = this.__promise.catch( ( reason ) => {
if ( !this.__isCanceled ) {
return onRejected( reason )
}
} )
return new CancelablePromise( newPromise )
}
}
/**
* Wrap a Promise that can be canceled.
* @type {CancelablePromiseWithCancelCallBack}
*/
module.exports = class CancelablePromiseWithCancelCallBack {
/**
* Creates a new CancelablePromise object. Should be noted it will not stop the promise from executing, rather.
* @class
* @constructor
* @param {function|Promise} executor - A Promise to wrap or the execution of a promise to be wrapped.
* @example
* function createPromise(timeout) {
* return new Promise((resolve, reject) => {
* setTimeout(() => {
* resolve('Hello, world!');
* }, timeout);
* });
* }
*
* // create a new promise that resolves after 2 seconds
* const myPromise = createPromise(2000);
*
* // create a new CancelablePromise that wraps the original promise
* const myCancelablePromise = new CancelablePromise(myPromise);
*
* // cancel the promise after 1 second
* setTimeout(() => {
* myCancelablePromise.cancel();
* }, 1000);
*
* // attach a handler to the promise that will be called when it is resolved
* myCancelablePromise.then(result => {
* console.log(result); // this will not be called because the promise was canceled
* }).catch(error => {
* console.error(error); // this will not be called because the promise was canceled
* });
*
* // add a cancel handler that will be called when the promise is canceled
* myCancelablePromise.onCancel(() => {
* console.log('Promise canceled!');
* });
*
* // create another promise that rejects after 3 seconds
* const anotherPromise = new Promise((resolve, reject) => {
* setTimeout(() => {
* reject(new Error('Something went wrong!'));
* }, 3000);
* });
*
* // create another CancelablePromise that wraps the original promise and allows up to 3 retries with a 1-second interval between them
* const myRetryablePromise = new RetryablePromise(anotherPromise, {
* retries: 3,
* interval: 1000
* });
*
* // attach a handler to the promise that will be called when it is resolved
* myRetryablePromise.then(result => {
* console.log(result); // this will not be called because the promise was rejected
* }).catch(error => {
* console.error(error); // this will be called after the third retry fails
* });
*
* // add a cancel handler that will be called when the promise is canceled
* myRetryablePromise.onCancel(() => {
* console.log('Promise canceled!');
* });
*/
constructor( executor ) {
/**
* The wrapped Promise object.
* @type {Promise}
*/
this.__promise = executor
if ( !( executor instanceof Promise ) ) {
this.__promise = new Promise( executor )
}
/**
* Whether the Promise has been canceled.
* @type {boolean}
*/
this.__isCanceled = false
/**
* A function that is called when the promise is canceled.
* @type {function}
*/
this.__cancelHandler = null
}
/**
* Cancels the Promise. And call the cancelHandler if one is defined.
*/
cancel() {
this.__isCanceled = true
if (this.__cancelHandler) {
this.__cancelHandler()
}
}
/**
* Attaches callbacks for the resolution and/or rejection of the Promise, and returns a new CancelablePromise object.
* @param {function} onFulfilled - A function that is called when the Promise is fulfilled. If the Promise is already fulfilled, the function is called immediately.
* @param {function} onRejected - A function that is called when the Promise is rejected. If the Promise is already rejected, the function is called immediately.
* @returns {Promise} A Promise for the completion of which every callback is executed.
*/
then( onFulfilled, onRejected ) {
if (this.__isCanceled) {
return Promise.resolve()
}
return this.__promise.then(onFulfilled, onRejected)
}
/**
* Attaches a callback for only the rejection of the Promise, and returns a new CancelablePromise object.
* @param {function} onRejected - A function that is called when the Promise is rejected. If the Promise is already rejected, the function is called immediately.
* @returns {CancelablePromise} A new CancelablePromise object.
*/
catch( onRejected ) {
if (this.__isCanceled) {
return Promise.resolve()
}
return this.__promise.catch(onRejected)
}
/**
* Attaches a callback that is called when the Promise is settled, regardless of whether it was fulfilled or rejected.
* @param {function} onFinally - A Function called when the Promise is settled.
* @returns {Promise} A Promise for the completion of the callback.
*/
finally( onFinally ) {
if ( this.__isCanceled ) {
return Promise.resolve()
}
return this.__promise.finally( onFinally )
}
/**
* Adds a function that is called when the promise is canceled.
* @param {function} cancelHandler - A function that is called when the promise is canceled.
*/
onCancel( cancelHandler ) {
this.__cancelHandler = cancelHandler
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment