UNPKG

@debonet/es6pacts

Version:

Releasable/cancellable and Reporting Promises for Javascript ES6

165 lines (114 loc) 7.57 kB
# es6pacts Releasable, reporting Promises for JavaScript ES6. ## TL;DR * A Pact is a Promise that can be released from its obligation (`release`, `resolve`, `reject`, `fulfill`) and that delivers progress reports to independent observers (`.progress`). * One line of composition: `fclassReleasable( Task )`. Release semantics come from `@debonet/es6pledges`; reporting semantics come from `@debonet/es6tasks`. * The constructor takes a single executor argument, `( fResolve, fReject, fReport ) => xPolicy`. The executor's RETURN VALUE is the release policy; the v2 second constructor argument is removed. * Progress reports stop when the pact settles. This is a library guarantee (post-settle suppression), not executor discipline. ## Example ```javascript const Pact = require( "@debonet/es6pacts" ); function fpactDelay( dtm, dtmTick = 100 ){ return new Pact(( fResolve, fReject, fReport ) => { let dtmElapsed = 0; const interval = setInterval(() => { dtmElapsed += dtmTick; fReport( dtmElapsed ); if ( dtmElapsed >= dtm ){ clearInterval( interval ); fResolve( dtm ); } }, dtmTick ); return () => clearInterval( interval ); }); } ``` Hold the SOURCE pact and release it; cancellation flows downstream as ordinary settlement: ```javascript const pactDelay = fpactDelay( 10000 ); pactDelay .progress(( dtmElapsed ) => console.log( "progress:", dtmElapsed )) .then(( x ) => console.log( "result:", x )); setTimeout(() => { pactDelay.resolve( "faster" ); }, 250 ); ``` Output: ``` progress: 100 progress: 200 result: faster ``` * The release at 250ms lands mid-gap between the 200ms and 300ms ticks, so the output is stable. * The cleanup is the executor's return value: `return () => clearInterval( interval );`. It clears the interval, and post-settle suppression guarantees no report after settlement. * The usage releases the source pact, not a chain tail. Releasing a derived pact settles only that pact and skips its own handler (see Chains). Same example, three libraries: remove the `fReport` executor parameter and the `fReport( dtmElapsed );` line and this is `fpledgeDelay` (`@debonet/es6pledges` — releasable, no reporting). Remove instead the `return () => clearInterval( interval );` line and this is `ftaskDelay` (`@debonet/es6tasks` — reporting, not releasable). All other lines are identical across the three libraries. Pact is the union, exactly as `fclassReleasable( Task )` is the union of the two libraries. ## Chain Windowing Using the same `fpactDelay`, attachment position selects what a `.progress` observer sees: ```javascript const pactFirst = fpactDelay( 10000 ); const pactDone = pactFirst .progress(( dtm ) => console.log( "first stage:", dtm )) .then(( x ) => fpactDelay( 300 )) .progress(( dtm ) => console.log( "whole chain:", dtm )); setTimeout(() => pactFirst.resolve( "faster" ), 350 ); pactDone.then(( x ) => console.log( "result:", x )); ``` Output: ``` first stage: 100 whole chain: 100 first stage: 200 whole chain: 200 first stage: 300 whole chain: 300 whole chain: 100 whole chain: 200 whole chain: 300 result: 300 ``` What the example shows: * The `.progress` attached BEFORE the `.then` observes only `pactFirst`'s stage. * The `.progress` attached AFTER the `.then` observes the forwarded parent stream plus the stream of the pact the handler returns. * The release arrives mid-chain: `pactFirst.resolve( "faster" )` settles the first stage, its reports stop (post-settle suppression), and the early value flows downstream as ordinary settlement. ## The Executor `new Pact( fxExecutor )` takes ONE argument. Task supplies `fReport` as the executor's third parameter, so the full signature is `( fResolve, fReject, fReport ) => xPolicy`. The return value `xPolicy` is the release policy: * A function `( xReason ) => ...` — the release handler. It closes over `fResolve`, `fReject`, `fReport`, and the executor's locals (the useEffect-cleanup idiom). * An Error instance — unconditional refusal: every `release()` rejects with it. * `undefined` or anything else — releasable with no cleanup. ## Release `pact.release( xReason )` runs a consent pipeline: the policy may clean up and consent, or refuse. On consent the pact settles by type dispatch on `xReason`: * `PactResolve` → resolves with its cause * `PactReject` → rejects with its cause * any other Error → rejects with it * anything else → resolves with it `Pact.PactResolve` and `Pact.PactReject` are statics on the class. The `release()` return contract: * FULFILLS with a settlement record `{ sStatus, x }` after the pact settles, ordered after handlers registered before the call. * REJECTS only on refusal (the release handler threw or rejected) while the pact was still pending. A refused pact stays pending, keeps all handlers, and keeps reporting. Settlement is a report that the obligation ended; nothing settles the pact without the release handler's consent. No forced settlement exists. ## Settle Verbs * `pact.resolve( x )``pact.release( new PactResolve( x ))` * `pact.reject( e )``pact.release( new PactReject( e ))` * `pact.fulfill( x )` — alias of `resolve( x )` All refusable, same pipeline. The instance `resolve` / `reject` are distinct from the inherited statics `Pact.resolve` / `Pact.reject`, which construct already-settled pacts. ## releaseOn `pact.releaseOn( signal )` requests a release when the AbortSignal fires, with `signal.reason` as `xReason`; it returns `this`. A bare `abort()` produces an AbortError, which rejects by dispatch; `abort( xValue )` resolves with the value. This is a refusable request, not an unconditional deadline. ## Chains Cancel a pipeline at its SOURCE: release the base pact you hold, and cancellation flows downstream as ordinary settlement. Releasing a derived pact never affects an ancestor; it settles the derived pact (and at most one owned child: an in-flight pact returned by its own stage handler). ## Reporting * `pact.progress( f )` registers an independent observer and returns `this` (chainable). * Every observer receives the SAME raw value passed to `fReport`, in attachment order. Observer return values are ignored. * Delivery is queued on a microtask. There is no replay buffer: attach late, miss earlier reports. * Once the pact settles, further reports are dropped by the library. * Derived pacts forward their parent's report stream, plus the stream of any pact a handler returns. Attachment position selects the observation window (see Chain Windowing). * Combinators (`Pact.all`, `any`, `race`, `allSettled`) re-emit member reports as `{ task : n, report : x }` and suppress them once the combined pact settles. Releasing a combined pact settles the combined pact only; members are not owned and keep running. ## Interlocking Guarantees * A released pact is a settled pact, so its reports stop — suppression is central, in the library. * A refused release leaves the pact pending, so reports keep flowing during and after the refusal. ## Full Semantics This module adds no behavior of its own; the complete contracts live upstream: * `@debonet/es6pledges` `specs/v3-core.md` — constructor, executor, policy, pipeline, dispatch, wrappers, release contract, verbs, `releaseOn`. * `@debonet/es6pledges` `specs/v3-chains-combinators.md` — chain and combinator release. * `@debonet/es6tasks` `specs/v3-reporting.md` — observer model, microtask delivery, post-settle suppression, windowing, combinator report format. ## See Also * @debonet/es6pledges * @debonet/es6tasks