-
-
Save domenic/8ed6048b187ee8f2ec75 to your computer and use it in GitHub Desktop.
// ES6 | |
class AngularPromise extends Promise { | |
constructor(executor) { | |
super((resolve, reject) => { | |
// before | |
return executor(resolve, reject); | |
}); | |
// after | |
} | |
then(onFulfilled, onRejected) { | |
// before | |
const returnValue = super.then(onFulfilled, onRejected); | |
// after | |
return returnValue; | |
} | |
} | |
// ES5 | |
function AngularPromise(executor) { | |
var p = new Promise(function (resolve, reject) { | |
// before | |
return executor(resolve, reject); | |
}); | |
// after | |
p.__proto__ = AngularPromise.prototype; | |
return p; | |
} | |
AngularPromise.__proto__ = Promise; | |
AngularPromise.prototype.__proto__ = Promise.prototype; | |
AngularPromise.prototype.then = function then(onFulfilled, onRejected) { | |
// before | |
var returnValue = Promise.prototype.then.call(this, onFulfilled, onRejected); | |
// after | |
return returnValue; | |
} |
Here's an example of a deferred promise, the constructor leverages defaults and might be close to what you wanted? @rektide https://gist.github.com/oliverfoster/00897f4552cef64653ef14d8b26338a6
@rektide here you go;
class YourPromise extends Promise {
resolve;
reject;
init = (resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
this.init = null;
return this;
};
constructor(callback, ...yourArgs) {
super(callback);
}
static async create(...yourArgs) {
let output;
const initArgs = await new Promise((resolve) => {
output = new YourPromise(
(...initArgs) => { resolve(initArgs); },
...yourArgs
);
});
//return an array because JS is a dick and unrolls all nested promises with a single `await` keyword
//INCLUDING the one we're deliberately trying to return AS A PROMISE (YourPromise extends Promise)
return [output.init(...initArgs)];
}
}
Instantiate via (await YourPromise.create('your', 'arguments'))[0]
@rektide, here is an implementation for deferred promise with class syntax:
`
class Deferred extends Promise {
/** @type {Function} /
#resolve;
/* @type {Function} */
#reject;
/**
- Creates new deferred promise.
- To create defered object DO NOT pass executor parameter!!!
- Parameter is only needed for Promise to work correctly.
- Somehow Promise calls constructor twice. First time when you
- create Promise. Second time - during first .then() with
- different executor.
- @param {undefined|null|Function} executor - DO NOT pass anything.
*/
constructor(executor) {
let res;
let rej;
executor = executor ?? ((resolve, reject) => {
res = resolve;
rej = reject;
});
super(executor);
if (res && rej) {
this.#resolve = res;
this.#reject = rej;
}
}
resolve(value) {
this.#resolve(value);
}
reject(err) {
this.#reject(err);
}
}
`
You can test it with:
`
const d = new Deferred();
d
.then((val) => {
console.log(Resolved with ${val}
);
}, (err) => {
console.log(Rejected with ${err}
);
});
d.resolve("RES");
// d.reject("REJ");
console.log("done");
`
@Aleksandras-Novikovas's implementation, with formatting/highlighting:
@rektide, here is an implementation for deferred promise with class syntax:
class Deferred extends Promise { /** @type {Function} */ #resolve; /** @type {Function} */ #reject; /** * Creates new deferred promise. * To create defered object DO NOT pass executor parameter!!! * Parameter is only needed for Promise to work correctly. * Somehow Promise calls constructor twice. First time when you * create Promise. Second time - during first .then() with * different executor. * @param {undefined|null|Function} executor - DO NOT pass anything. */ constructor(executor) { let res; let rej; executor = executor ?? ((resolve, reject) => { res = resolve; rej = reject; }); super(executor); if (res && rej) { this.#resolve = res; this.#reject = rej; } resolve(value) { this.#resolve(value); } reject(err) { this.#reject(err); } } }You can test it with:
const d = new Deferred(); d.then((val) => { console.log(`Resolved with ${val}`); }, (err) => { console.log(`Rejected with ${err}`); }); d.resolve("RES"); // d.reject("REJ"); console.log("done");
I've written much cleaner version: timeout-promise
This is what I came up with after a few hours of debugging and trial+error. It stores the call stack of the location where it is instantiated, allowing rejectWithError()
to produce useful errors even when it is called from a parallel asynchronous process, e.g. an event handler.
export class DeferredPromise<T> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason: T | Error) => void;
initialCallStack: Error['stack'];
constructor(executor: ConstructorParameters<typeof Promise<T>>[0] = () => {}) {
let resolver: (value: T | PromiseLike<T>) => void;
let rejector: (reason: T | Error) => void;
super((resolve, reject) => {
resolver = resolve;
rejector = reject;
return executor(resolve, reject); // Promise magic: this line is unexplicably essential
});
this.resolve = resolver!;
this.reject = rejector!;
// store call stack for location where instance is created
this.initialCallStack = Error().stack?.split('\n').slice(2).join('\n');
}
/** @throws error with amended call stack */
rejectWithError(error: Error) {
error.stack = [error.stack?.split('\n')[0], this.initialCallStack].join('\n');
this.reject(error);
}
}
You can use it like this:
const deferred = new DeferredPromise();
/* resolve */
deferred.resolve(value);
await deferred;
/* reject */
deferred.reject(Error(errorMessage));
await deferred; // throws Error(errorMessage) with current call stack
/* reject */
deferred.rejectWithError(Error(errorMessage));
await deferred; // throws Error(errorMessage) with amended call stack
/* reject with custom error type */
class CustomError extends Error {}
deferred.rejectWithError( new CustomError(errorMessage) );
await deferred; // throws CustomError(errorMessage) with amended call stack
Example use in my own project:
deferred-promise.ts
usage in badge-usb.ts > BadgeUSB._handlePacket()
You don't have to define constructor argument If you don't need the value returned by .then
method to be of your class instance.
Example:
class DeferredPromise extends Promise {
static get [Symbol.species]() {
return Promise;
}
constructor() {
let internalResolve = () => { };
let internalReject = () => { };
super((resolve, reject) => {
internalResolve = resolve;
internalReject = reject;
});
this.resolve = internalResolve;
this.reject = internalReject;
}
}
This is what I came up with after a few hours of debugging and trial+error. It stores the call stack of the location where it is instantiated, allowing
rejectWithError()
to produce useful errors even when it is called from a parallel asynchronous process, e.g. an event handler.export class DeferredPromise<T> extends Promise<T> { resolve: (value: T | PromiseLike<T>) => void; reject: (reason: T | Error) => void; initialCallStack: Error['stack']; constructor(executor: ConstructorParameters<typeof Promise<T>>[0] = () => {}) { let resolver: (value: T | PromiseLike<T>) => void; let rejector: (reason: T | Error) => void; super((resolve, reject) => { resolver = resolve; rejector = reject; return executor(resolve, reject); // Promise magic: this line is unexplicably essential }); this.resolve = resolver!; this.reject = rejector!; // store call stack for location where instance is created this.initialCallStack = Error().stack?.split('\n').slice(2).join('\n'); } /** @throws error with amended call stack */ rejectWithError(error: Error) { error.stack = [error.stack?.split('\n')[0], this.initialCallStack].join('\n'); this.reject(error); } }You can use it like this:
const deferred = new DeferredPromise(); /* resolve */ deferred.resolve(value); await deferred; /* reject */ deferred.reject(Error(errorMessage)); await deferred; // throws Error(errorMessage) with current call stack /* reject */ deferred.rejectWithError(Error(errorMessage)); await deferred; // throws Error(errorMessage) with amended call stack /* reject with custom error type */ class CustomError extends Error {} deferred.rejectWithError( new CustomError(errorMessage) ); await deferred; // throws CustomError(errorMessage) with amended call stackExample use in my own project: deferred-promise.ts usage in badge-usb.ts >
BadgeUSB._handlePacket()
It works like a magic. Thanks for your work.
@eKoopmans: my hope would be that we could have some interesting ways to extend Promise that used the
extends
keyword & class syntax. Alas.None the less, good job with your hackery of Promise.