An explicitly non-A+ Promise library that resolves promises synchronously
MIT License
Yet another promise library, but this one is designed intentionally against the ES6 promise pattern, which asynchronously resolves promise callbacks in the next tick of the JS engine. In many cases, asynchronous resolution is the safest and easiest-to-understand implementation of a promise, but it adds a huge delay to the resolution, which in most places is unnecessary. Moreover, when we attempted to wrap the IndexedDB architecture with standard ES6 promises, it falls apart, because IndexedDB closes database connections when control is passed back to the main thread. We started building NoSQLProvider and immediately ran into this problem. SyncTasks is the solution to that problem, but is also a performant answer to asynchronous programming problems in general. In addition, we've worked in a simple optional cancellation mechanism that chains through promise resolution as well (as long as you chain through SyncTasks promises, and don't mix in non-cancellation-supported promises.)
Usage of SyncTasks promises is somewhat similar to JQuery promises. If you need to create a promise deferral, you call
SyncTasks.Defer()
and it returns a SyncTasks.Deferred
object. The usual flow is to stash the deferral away for your async
logic to later resolve/reject, and call .promise()
on the deferral and return that to your caller, so that the caller only
works with the promise (for chaining and resolution-callback reasons). Any calls of resolve/reject are synchronously resolved
before returning from the resolve/reject method.
SyncTasks has the basic Defer
call, but also helper methods to save on common tasks for interacting with promises/deferrals:
Defer()
- Returns a new blank SyncTasks.Deferral
.Resolved(obj?)
- Returns a new SyncTasks.Promise
, already in a resolved (success) state, with the optional passedRejected(obj?)
- Returns a new SyncTasks.Promise
, already in a rejected (failure) state, with the optional passedall([promises...])
- Returns a new SyncTasks.Promise
that will resolve when all of the promises in the list have finished.all
will resolve with failure, with the first non-undefined
resolutionall
. If all are successful, then it will resolve successfully, with the resolution valuerace([promises...])
- Returns a new SyncTasks.Promise
that will resolve when any one of the promises in the list finish.asyncCallback(callback)
- Runs the specified callback function on the next tick of the javascript engine. Uses a sharedthenAsync
to queue the callbacks for the next tick, but is also a handy helper for more optimized defering of multiplefromThenable(thenable)
- A handy helper function to wrap any sort of Thenable
(usually used for wrapping ES6 Promises) intoSyncTasks.Promise
that resolve andsetTracingEnabled(boolean)
- This option allows enabling of double resolution tracing individually per Promise.A created Deferral
object only has 4 methods:
resolve(obj?)
- Resolves any promises created by the deferral with success. Takes an optional value that is passed through thereject(obj?)
- Resolves any promises created by the deferral with failure. Takes an optional value that is passed through theonCancel(callback)
- Adds a cancellation callback function that is called whenever non-resolved/rejected promises created by the deferral get .cancel
promise()
- Returns a SyncTasks.Promise
object from the deferral, which is then passed around the world.The Promise
object is the public face of the async process that your Deferred
is managing. You add various callbacks to
the promise object depending on what types of events you want to get notified about, and what type of chaining you want to
support. The methods supported are:
then(successCallback, failureCallback?)
- The most common resolution mechanism for a promise. If the promise successfullythen
call returns a new promise which will be resolvedthenAsync(successCallback?, failureCallback?)
- Has the same nuances and behavior as then
, but the callbacks are calledalways(callback)
- A synonym for calling then
with the same callback for both parameters. Use this when you want tothen
, returnscatch(callback)
- Has the same effect as calling then
with the specified callback for the failureCallback parameter andthen
, returns a new promise fordone(callback)
, fail(callback)
, and finally(callback)
- If you would like to observe, but not change, the resolutiondone
is only calledfail
is only called if the resolution chain is failure, and finally
is calledfinally
's case, you havecancel(obj?)
- This method will notify the original deferral object of cancellation, and will pass it the optional valuetoEs6Promise()
- A helper function that wraps SyncTasks.Promise
object "back" into ES6 Promise. It directly maps successresolve
and reject
function sendMeAStringLater(numberOfMilliseconds: number, theString: string): SyncTasks.Promise<string> {
let defer = SyncTasks.Defer<string>();
setTimeout(() => {
defer.resolve(theString);
}, numberOfMilliseconds);
return defer.promise();
}
sendMeAStringLater(500, 'hi').then(myString => {
console.log(myString);
});
// 500 ms after running this, you will end up with a new console log line, "hi".
function sendMeAStringLater(numberOfMilliseconds: number, theString: string): SyncTasks.Promise<string> {
let defer = SyncTasks.Defer<string>();
let didFinish = false;
defer.onCancel(whyWasICancelled => {
if (!didFinish) {
didFinish = true;
defer.reject(whyWasICancelled);
}
});
setTimeout(() => {
// Make sure to bail here if it's already done. If you resolve a second time, it will throw an exception, since the
// cancel already resolved it once.
if (!didFinish) {
didFinish = true;
defer.resolve(theString);
}
}, numberOfMilliseconds);
return defer.promise();
}
let promise = sendMeAStringLater(500, 'hi').then(myString => {
console.log('Success: ' + myString);
}, errString => {
console.log('Failure: ' + errString);
});
setTimeout(() => {
promise.cancel('Sorry');
}, 200);
// 200 ms after running this, you will end up with a new console log line, "Failure: Sorry". The success case will not be
// run because it was already resolved with failure. If you change the 200ms timer to 600ms, then your console will change to
// "Success: hi" because the cancellation will happen after the success already did, so the `didFinish` check will swallow it.