@open-draft/deferred-promise
Advanced tools
Comparing version 2.0.0 to 2.1.0
export declare type PromiseState = 'pending' | 'fulfilled' | 'rejected'; | ||
export declare type ResolveFunction<Data extends any, Result = void> = (value: Data) => Result | PromiseLike<Result>; | ||
export declare type RejectFunction<Result = unknown> = (reason?: unknown) => Result | PromiseLike<Result>; | ||
export declare type DeferredPromiseExecutor<Input = void, Output = Input> = { | ||
(resolve?: ResolveFunction<any, unknown>, reject?: RejectFunction): void; | ||
resolve: ResolveFunction<Input, Output | void>; | ||
reject: RejectFunction; | ||
result?: Output | Input; | ||
export declare type Executor<Value> = ConstructorParameters<typeof Promise<Value>>[0]; | ||
export declare type ResolveFunction<Value> = Parameters<Executor<Value>>[0]; | ||
export declare type RejectFunction<Reason> = Parameters<Executor<Reason>>[1]; | ||
export declare type DeferredPromiseExecutor<Input = never, Output = Input> = { | ||
(resolve?: ResolveFunction<Input>, reject?: RejectFunction<any>): void; | ||
resolve: ResolveFunction<Input>; | ||
reject: RejectFunction<any>; | ||
result?: Output; | ||
state: PromiseState; | ||
@@ -10,0 +11,0 @@ rejectionReason?: unknown; |
@@ -8,18 +8,20 @@ "use strict"; | ||
executor.resolve = (data) => { | ||
queueMicrotask(() => { | ||
if (executor.state === 'pending') { | ||
executor.state = 'fulfilled'; | ||
executor.result = data; | ||
resolve(data); | ||
} | ||
}); | ||
if (executor.state !== 'pending') { | ||
return; | ||
} | ||
executor.result = data; | ||
const onFulfilled = (value) => { | ||
executor.state = 'fulfilled'; | ||
return value; | ||
}; | ||
return resolve(data instanceof Promise ? data : Promise.resolve(data).then(onFulfilled)); | ||
}; | ||
executor.reject = (reason) => { | ||
if (executor.state !== 'pending') { | ||
return; | ||
} | ||
queueMicrotask(() => { | ||
if (executor.state === 'pending') { | ||
executor.state = 'rejected'; | ||
executor.rejectionReason = reason; | ||
reject(reason); | ||
} | ||
executor.state = 'rejected'; | ||
}); | ||
return reject((executor.rejectionReason = reason)); | ||
}; | ||
@@ -26,0 +28,0 @@ }); |
@@ -1,23 +0,12 @@ | ||
import { PromiseState, ResolveFunction, RejectFunction } from './createDeferredExecutor'; | ||
/** | ||
* @example | ||
* function getPort() { | ||
* const portReadyPromise = new DeferredPromise<number>() | ||
* port.on('ready', (port) => portReadyPromise.resolve(port)) | ||
* port.on('error', (error) => portReadyPromise.reject(error)) | ||
* returtn portReadyPromise | ||
* } | ||
*/ | ||
export declare class DeferredPromise<Input = never, Output = Input> { | ||
import { type Executor, type RejectFunction, type ResolveFunction } from './createDeferredExecutor'; | ||
export declare class DeferredPromise<Input, Output = Input> extends Promise<Input> { | ||
#private; | ||
resolve: (value: Input) => void; | ||
reject: (reason?: unknown) => void; | ||
constructor(); | ||
static get [Symbol.species](): PromiseConstructor; | ||
get [Symbol.toStringTag](): string; | ||
get state(): PromiseState; | ||
resolve: ResolveFunction<Output>; | ||
reject: RejectFunction<Output>; | ||
constructor(executor?: Executor<Input> | null); | ||
get state(): import("./createDeferredExecutor").PromiseState; | ||
get rejectionReason(): unknown; | ||
then<FulfillmentResult = Output, RejectionResult = never>(onFulfilled?: ResolveFunction<Output, FulfillmentResult> | null, onRejected?: RejectFunction<RejectionResult> | null): DeferredPromise<Input, FulfillmentResult | RejectionResult>; | ||
catch<Result>(onRejected?: RejectFunction<Result>): DeferredPromise<Input, Output | Result>; | ||
finally(onSettled?: (() => void) | null): DeferredPromise<Input, Output>; | ||
then<ThenResult = Input, CatchResult = never>(onFulfilled?: (value: Input) => ThenResult | PromiseLike<ThenResult>, onRejected?: (reason: any) => CatchResult | PromiseLike<CatchResult>): DeferredPromise<ThenResult | CatchResult, Output>; | ||
catch<CatchResult = never>(onRejected?: (reason: any) => CatchResult | PromiseLike<CatchResult>): DeferredPromise<Input | CatchResult, Output>; | ||
finally(onfinally?: () => void | Promise<any>): DeferredPromise<Input, Output>; | ||
} |
@@ -5,28 +5,16 @@ "use strict"; | ||
const createDeferredExecutor_1 = require("./createDeferredExecutor"); | ||
/** | ||
* @example | ||
* function getPort() { | ||
* const portReadyPromise = new DeferredPromise<number>() | ||
* port.on('ready', (port) => portReadyPromise.resolve(port)) | ||
* port.on('error', (error) => portReadyPromise.reject(error)) | ||
* returtn portReadyPromise | ||
* } | ||
*/ | ||
class DeferredPromise { | ||
#promise; | ||
class DeferredPromise extends Promise { | ||
#executor; | ||
resolve; | ||
reject; | ||
constructor() { | ||
this.#executor = (0, createDeferredExecutor_1.createDeferredExecutor)(); | ||
this.#promise = new Promise(this.#executor); | ||
constructor(executor = null) { | ||
const deferredExecutor = (0, createDeferredExecutor_1.createDeferredExecutor)(); | ||
super((originalResolve, originalReject) => { | ||
deferredExecutor(originalResolve, originalReject); | ||
executor?.(deferredExecutor.resolve, deferredExecutor.reject); | ||
}); | ||
this.#executor = deferredExecutor; | ||
this.resolve = this.#executor.resolve; | ||
this.reject = this.#executor.reject; | ||
} | ||
static get [Symbol.species]() { | ||
return Promise; | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'DeferredPromise'; | ||
} | ||
get state() { | ||
@@ -39,89 +27,14 @@ return this.#executor.state; | ||
then(onFulfilled, onRejected) { | ||
const derivedPromise = new DeferredPromise(); | ||
const resolveDerivedPromise = (result) => { | ||
if (typeof onFulfilled === 'function') { | ||
try { | ||
const nextResult = onFulfilled(result); | ||
derivedPromise.#executor.resolve(nextResult); | ||
} | ||
catch (error) { | ||
rejectDerivedPromise(error); | ||
} | ||
return; | ||
} | ||
/** | ||
* @note Use the executor directly because the "resolve" method | ||
* always gets overridden to point to the previous Promise in the chain. | ||
*/ | ||
derivedPromise.#executor.resolve(result); | ||
}; | ||
const rejectDerivedPromise = (result) => { | ||
if (typeof onRejected === 'function') { | ||
try { | ||
const nextReason = onRejected(result); | ||
/** | ||
* @note If the rejection has a handler callback ("catch"), | ||
* resolve the chained promise instead of rejecting it. | ||
* The "catch" callback is there precisely to prevent | ||
* uncaught promise rejection errors. | ||
*/ | ||
derivedPromise.#executor.resolve(nextReason); | ||
/** | ||
* @note Since the handled rejected promise was resolved internally, | ||
* still mark its state as rejected. Do so in the next microtask | ||
* because executor rejects the promise in the next task as well | ||
*/ | ||
queueMicrotask(() => { | ||
derivedPromise.#executor.state = 'rejected'; | ||
derivedPromise.#executor.rejectionReason = nextReason; | ||
}); | ||
} | ||
catch (error) { | ||
rejectDerivedPromise(error); | ||
} | ||
return; | ||
} | ||
derivedPromise.#executor.reject(result); | ||
}; | ||
switch (this.state) { | ||
case 'pending': { | ||
/** | ||
* @note Override the chainable "resolve" so that when called | ||
* it executes the resolve of the Promise from which it was | ||
* chained from. | ||
* | ||
* @example | ||
* const p1 = new DeferredPromise().then(A).then(B) | ||
* p1.resolve() | ||
* | ||
* Here, "p1" points to the rightmost promise (B) but the | ||
* order of resolving the value must be left-to-right: | ||
* Initial -> A -> B | ||
*/ | ||
derivedPromise.resolve = this.resolve; | ||
derivedPromise.reject = this.reject; | ||
this.#promise.then(resolveDerivedPromise, rejectDerivedPromise); | ||
break; | ||
} | ||
case 'fulfilled': { | ||
resolveDerivedPromise(this.#executor.result); | ||
break; | ||
} | ||
case 'rejected': { | ||
rejectDerivedPromise(this.#executor.rejectionReason); | ||
break; | ||
} | ||
} | ||
return derivedPromise; | ||
return this.#decorate(super.then(onFulfilled, onRejected)); | ||
} | ||
catch(onRejected) { | ||
return this.then(undefined, onRejected); | ||
return this.#decorate(super.catch(onRejected)); | ||
} | ||
finally(onSettled) { | ||
return this.then((result) => { | ||
onSettled?.(); | ||
return result; | ||
}, (reason) => { | ||
onSettled?.(); | ||
throw reason; | ||
finally(onfinally) { | ||
return this.#decorate(super.finally(onfinally)); | ||
} | ||
#decorate(promise) { | ||
return Object.defineProperties(promise, { | ||
resolve: { configurable: true, value: this.resolve }, | ||
reject: { configurable: true, value: this.reject }, | ||
}); | ||
@@ -128,0 +41,0 @@ } |
{ | ||
"name": "@open-draft/deferred-promise", | ||
"version": "2.0.0", | ||
"description": "A Promise that can be resolved/rejected elsewhere", | ||
"version": "2.1.0", | ||
"description": "A Promise-compatible abstraction that defers resolving/rejecting promises to another closure.", | ||
"main": "./build/index.js", | ||
@@ -9,2 +9,3 @@ "typings": "./build/index.d.ts", | ||
"test": "jest", | ||
"test:compliance": "export NODE_OPTIONS=--loader=tsx || set NODE_OPTIONS=--loader=tsx&& npx -y promises-aplus-tests ./test/aplus-tests-adapter.ts", | ||
"prebuild": "rimraf ./build", | ||
@@ -22,3 +23,4 @@ "build": "tsc", | ||
"resolve", | ||
"reject" | ||
"reject", | ||
"executor" | ||
], | ||
@@ -33,4 +35,5 @@ "author": "Artem Zakharchenko", | ||
"ts-jest": "^29.0.0", | ||
"tsx": "^3.12.1", | ||
"typescript": "^4.8.3" | ||
} | ||
} |
@@ -186,1 +186,7 @@ # Deferred Promise | ||
See [`DeferredExecutor.rejectionReason`](#deferredexecutorrejectionreason) | ||
--- | ||
## Mentions | ||
- [Jonas Kuske](https://github.com/jonaskuske) for the phenomenal work around improving Promise-compliance. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
24932
14
276
192
7