Home > Software engineering >  Extending a JavaScript Promise
Extending a JavaScript Promise

Time:10-11

Note:

This question has been closed as a duplicate, though I can’t see how it is the same as the referenced question.

Here I am asking about extending the Promise class. There the question is different, though it does mention a common goal of accessing the executor functions.


I am trying to extend JavaScript Promises using ES6 class syntax. In the extended class, I want to make the resolve and reject functions more accessible.

Here is a test script:

var executor = {};
var promise = new Promise((resolve,reject) => {
  executor = {resolve,reject};
});
promise.resolve = message => { executor.resolve(message ?? true); };
promise.reject = message => { executor.reject(message ?? false); };

document.querySelector('button#ok').onclick = event => promise.resolve('OK');
document.querySelector('button#cancel').onclick = event => promise.reject('Cancelled');

promise
.then(result => {console.log(result); })
.catch(error => {console.log(error); });
<button id="ok" type="button">OK</button>
<button id="cancel" type="button">Cancel</button>

Ultimately the code will be part of a pseudo dialog box.

As it stands, the Promise constructor stores the resolve and reject functions in an external variable, and two additional methods are bolted on to the resulting promise object.

I thought it should be s simple task to do this in an an inherited object:

class Promise2 extends Promise {
    constructor() {
        //  add the resolve and resolve functions as instance methods
    }
}

The problem is that the constructor needs to call super() to instantiate this and I can’t see how I can proceed from there.

Is it possible to extend Promise this way, or is there another way to store a reference to the resolve and reject functions in the object itself?

CodePudding user response:

You can simply store the resolve/reject functions in the promise object itself to make them accessible. They are passed to the callback in the constructor, so you can instead pass your own callback there, save them, and then call the original callback:

class Promise2 extends Promise {
  constructor (callback) {
    super((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject
      
      callback?.(resolve, reject)
    })
  }
}

(I used optional chaining on the function call to support omitting the callback argument too.)

CodePudding user response:

IMO, the simplest solution is to use the Deferred object that I referenced in the above comment and avoid sub-classing promises. There are complications when you subclass a promise because when someone calls .then() or .catch() on your sub-classed promise, those functions return a new promise and the system will try to make that new promise be your class. So your sub-class of the promise MUST fully support regular promise behavior in addition to your own. Debugging this stuff can be really, really confusing because your constructor gets called a lot more than you think it should because of all the system-created promises being constructed.

So, my recommendation is to use a Deferred as explained in this answer and it should fully support what you want to do without any of the complications of subclassing a promise.


If you really want to subclass a promise, you can do something like this:

class PromiseDeferred extends Promise {
    constructor(executor) {
        let res, rej;
        super((resolve, reject) => {
            res = resolve;
            rej = reject;
        });
        this.resolve = res;
        this.reject = rej;

        // when someone uses .then() or .catch() on our PromiseDeferred
        // that action creates a new promise and it will create one using
        // our class.  It will, however, call our constructor with a
        // regular executor function and expect it to work normally
        // so we have to support that behavior
        if (executor) {
            executor(res, rej);
        }
    }
}

// sample usage

let d1 = new PromiseDeferred();
d1.then(val => {
    console.log('Promise d1 resolved with', val);
}).catch(err => {
    console.log(`Promise d1 rejected with message: ${err.message}`);
});
d1.resolve("Hello");

// -----------------------------------------

let d2 = new PromiseDeferred();
d2.then(val => {
    console.log('Promise d2 resolved with', val);
}).catch(err => {
    console.log(`Promise d2 rejected with message: ${err.message}`);
});
d2.reject(new Error("Promise rejected"));

  • Related