Extending a Promise in javascript
As @tobuslieven's answer suggested, it's not necessary to extend Promise. Below is a simplified sample.
ES6:
class MyPromise {
async executor () {
if(!this.name) {
throw new Error('whoops!')
}
console.log('Hello', this.name)
}
then () {
const promise = this.executor()
return promise.then.apply(promise, arguments)
}
catch () {
const promise = this.executor()
return promise.catch.apply(promise, arguments)
}
}
ES5:
function MyPromise () { }
MyPromise.prototype.executor = function () {
var self = this
return new Promise(function (resolve, reject) {
if (!self.name) {
return reject(new Error('whoops!'))
}
console.log('Hello', self.name)
resolve()
})
}
MyPromise.prototype.then = function () {
var promise = this.executor()
return promise.then.apply(promise, arguments)
}
MyPromise.prototype.catch = function () {
var promise = this.executor()
return promise.catch.apply(promise, arguments)
}
both can be tested by:
var promise = new MyPromise()
promise.name = 'stackoverflow'
promise
.then(function () {
console.log('done!')
})
.catch(console.log)
My latest solution is to compose a Promise object into my class as this.promise and then pretend to be inheriting from Promise by overriding all the instance methods of Promise and passing them on to the this.promise object. Hilarity ensues. I'd really welcome people pointing out the drawbacks to this approach.
Nothing is too obvious for me to have missed.
When I paste this code into the Chrome console, it seems to work. That's as far as I comprehend.
Cheers for taking a look.
BatchAjax = function(queries) {
var batchAjax = this;
this.queries = queries;
this.responses = [];
this.errorCount = 0;
this.promise = new Promise(function(resolve, reject) {
batchAjax.executor(resolve, reject);
});
};
BatchAjax.prototype = Object.create(Promise.prototype);
BatchAjax.prototype.constructor = BatchAjax;
BatchAjax.prototype.catch = function(fail) {
return this.promise.catch(fail);
}
BatchAjax.prototype.then = function(success, fail) {
return this.promise.then(success, fail);
};
BatchAjax.prototype.executor = function(resolve, reject) {
var batchAjax = this;
$.each(this.queries, function(index) {
var query = this;
query.success = function (result) {
batchAjax.processResult(result, index, resolve, reject);
};
query.error = function (jqXhr, textStatus, errorThrown) {
batchAjax.errorCount++;
var result = {jqXhr: jqXhr, textStatus: textStatus, errorThrown: errorThrown};
batchAjax.processResult(result, index, resolve, reject);
};
$.ajax(query);
});
};
BatchAjax.prototype.processResult = function(result, index, resolve, reject) {
this.responses[index] = result;
if (this.responses.length === this.queries.length) {
if (this.errorCount === 0) {
resolve(this.responses);
} else {
reject(this.responses);
}
}
};
// Usage
var baseUrl = 'https://jsonplaceholder.typicode.com';
(new BatchAjax([{url: baseUrl + '/todos/4'}, {url: baseUrl + '/todos/5'}]))
.then(function(response) {console.log('Yay! ', response);})
.catch(function(error) {console.log('Aww! ', error);});
The native Promise
class (like Error
and Array
) cannot be correctly subclassed with the old ES5-style mechanism for subclassing.
The correct way to subclass Promise
is through class
syntax:
class MyPromise extends Promise {
}
Example:
class MyPromise extends Promise {
myMethod() {
return this.then(str => str.toUpperCase());
}
}
// Usage example 1
MyPromise.resolve("it works")
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));
// Usage example 2
new MyPromise((resolve, reject) => {
if (Math.random() < 0.5) {
resolve("it works");
} else {
reject(new Error("promise rejected; it does this half the time just to show that part working"));
}
})
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));
If it's your goal to do that without class
, using mostly ES5-level features, you can via Reflect.construct
. Note that Reflect.construct
is an ES2015 feature, like class
, but you seem to prefer the ES5 style of creating classes.
Here's how you do that:
// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
return Reflect.construct(Promise, [executor], MyPromise);
};
// Make `MyPromise` inherit statics from `Promise`
Object.setPrototypeOf(MyPromise, Promise);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
return this.then(str => str.toUpperCase());
};
Then use it just like Promise
:
MyPromise.resolve("it works")
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));
or
new MyPromise(resolve => resolve("it works"))
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));
etc.
Live Example:
// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
return Reflect.construct(Promise, [executor], MyPromise);
};
// Make `MyPromise` inherit statics from `Promise`
Object.setPrototypeOf(MyPromise, Promise);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
return this.then(str => str.toUpperCase());
};
// Usage example 1
MyPromise.resolve("it works")
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));
// Usage example 2
new MyPromise((resolve, reject) => {
if (Math.random() < 0.5) {
resolve("it works");
} else {
reject(new Error("promise rejected; it does this half the time just to show that part working"));
}
})
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));
If you want to avoid changing the prototype of MyPromise
, you can copy the static properties over, but it's not quite the same thing:
// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
return Reflect.construct(Promise, [executor], MyPromise);
};
// Assign the statics (`resolve`, `reject`, etc.) to the new constructor
Object.assign(
MyPromise,
Object.fromEntries(
Reflect.ownKeys(Promise)
.filter(key => key !== "length" && key !== "name")
.map(key => [key, Promise[key]])
)
);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
return this.then(str => str.toUpperCase());
};
Using it is the same, of course.
Live Example:
// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
return Reflect.construct(Promise, [executor], MyPromise);
};
// Assign the statics (`resolve`, `reject`, etc.) to the new constructor
Object.assign(
MyPromise,
Object.fromEntries(
Reflect.ownKeys(Promise)
.filter(key => key !== "length" && key !== "name")
.map(key => [key, Promise[key]])
)
);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
return this.then(str => str.toUpperCase());
};
// Usage example 1
MyPromise.resolve("it works")
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));
// Usage example 2
new MyPromise((resolve, reject) => {
if (Math.random() < 0.5) {
resolve("it works");
} else {
reject(new Error("promise rejected; it does this half the time just to show that part working"));
}
})
.myMethod()
.then(result => console.log(result))
.catch(error => console.error(error));