Some things that are "better" with this BetterPromise
implementation:
-
BetterPromise # then(..)
accepts aBetterPromise
(orPromise
) instance passed directly, instead of requiring a function to return it, so that the promise is linked into the chain.var p = BetterPromise.resolve(42); var q = Promise.resolve(10); p.then(console.log).then(q).then(console.log); // 42 // 10
-
BetterPromise # unthen(..)
/BetterPromise # uncatch(..)
/BetterPromise # unfinally(..)
allows you to unregister a fulfillment or rejection handler that you previously registered on a promise viathen(..)
orcatch(..)
, or unregister a resolution handler that you previously registered on a promise viafinally(..)
.NOTE: This seems to be the majority use-case for why many like/want "promise cancelation" -- IOW, often what you want is to just stop observing a promise's resolution, not actually forcibly cancel the operation is comes from.
var p = new BetterPromise(function(res){ setTimeout(function(){ res(42); },100); }); function f1(v) { console.log(`f1: ${v}`); } function f2(v) { console.log(`f2: ${v}`); } function f3() { console.log("finally!"); } p.then(f1); p.then(f2); p.finally(f3); p.unthen(f1); p.unfinally(f3); // later // f2: 42
-
BetterPromise # finally(..)
is included (assumed implemented since it's already stage-4). Registers a resolution handler which is called on either fulfillment or rejection, sorta like if you didthen( fn, fn )
(but not exactly).var p = BetterPromise.resolve(42); p .finally(function(){ console.log("resolved!"); }) .then(function(v){ console.log(`still: ${v}`); }); // resolved! // still: 42
-
BetterPromise # thenLog()
/BetterPromise # catchLog()
inserts a step in a promise chain that simply prints the value to the console (console.log
for fulfillment,console.error
for rejection) and passes the value or rejection along to the next step untouched.var p = BetterPromise.resolve(42); p .thenLog() .then(function(v){ console.log(`still: ${v}`); }); // 42 // still: 42
-
Instead of silently being swallowed, if a synchronous exception is thrown in a
BetterPromise
constructor after the promise has already been synchronously resolved (fulfillment or rejection), that exception overrides and causes the promise to be rejected with that exception.var p = new BetterPromise(function(res){ res(42); throw 10; }); p.then( function(v){ console.log(`then: ${v}`); }, function(e){ console.log(`oops: ${e}`); } ); // oops: 10
-
BetterPromise.try(..)
(static helper) is implemented (so not just draft). Runs a function (with no arguments) synchronously, returns a promise for its return value (or adopts its promise), catches any synchronous exception and turns it into a rejection.var p = BetterPromise.try(function(){ undefined(42); }); p.catch(console.log); // TypeError: undefined is not a function
-
BetterPromise.deferred(..)
(static helper) constructs an instance of the promise, but also extracts itsresolve(..)
andreject(..)
capabilities, and returns all 3 in an object (aka, a "deferred").var { pr, resolve, reject } = BetterPromise.deferred(); pr.then(console.log); resolve(42); // 42
-
BetterPromise.lift(..)
(static helper) lifts an error-first, callback-last style function to beBetterPromise
returning instead.var readFile = BetterPromise.lift(fs.readFile); readFile("/path/to/file.txt") .then(printContents);
-
BetterPromise.control(..)
(static helper) wraps a function so that when called, it first creates anAbortController
instance, passes in itssignal
as the first argument to the original function, and returns a controller object that has both apr
for the function's completion, as well as acancel(..)
to send the abort signal. Ostensibly, the original function can then observe/respond to that passed-in signal and do something appropriate with it, like canceling its own behavior, passing it tofetch(..)
to abort an Ajax call, etc.async function main(signal,url) { signal.addEventListener("abort", .. ); // .. var resp = await fetch(url, { signal }); // .. } var fn = BetterPromise.control(main); var { pr, cancel } = fn("http://some.url"); pr.then(..); // later cancel(); // sends cancelation signal into `fn(..)`
Interesting thought experiment :)
My main feedback is that keeping the API surface of
Promise
as small as possible is important.With that being said, of these changes, I like
Promise#finally
andPromise.try
.As for
Promise#unthen
etc, I get what you're saying with the majority use case, but the naming is awkward and imho would be confusing for peeps.The one missing item which I'm surprised you didn't include would be concurrency control for evaluating multiple Promise-returning functions which is what I want 95% of the time I would've used
Promise.all
. I typically use p-map to accomplish this, but given how often it's used, in terms of this thought experiment, I would love to addPromise.map
as a first-class version of this functionality.Cheers!