synctasks
v0.3.3
Published
An explicitly non-A+ Promise library that resolves promises synchronously
Downloads
1,245
Readme
SyncTasks
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
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 Static Reference
SyncTasks has the basic Defer
call, but also helper methods to save on common tasks for interacting with promises/deferrals:
Defer()
- Returns a new blankSyncTasks.Deferral
.Resolved(obj?)
- Returns a newSyncTasks.Promise
, already in a resolved (success) state, with the optional passed resolution value.Rejected(obj?)
- Returns a newSyncTasks.Promise
, already in a rejected (failure) state, with the optional passed resolution value.all([promises...])
- Returns a newSyncTasks.Promise
that will resolve when all of the promises in the list have finished. If any of the promises resolved with failure, thenall
will resolve with failure, with the first non-undefined
resolution value as the resolution value forall
. If all are successful, then it will resolve successfully, with the resolution value being an array of the resolution values from the input promises. You can also pass values instead of promises in the input list, and those values will pass through as successful resolutions to the output list.race([promises...])
- Returns a newSyncTasks.Promise
that will resolve when any one of the promises in the list finish. Resolves or rejects the output promise based on the resolution type and value of the first finished promise in the list.asyncCallback(callback)
- Runs the specified callback function on the next tick of the javascript engine. Uses a shared queue, so that all callbacks are played on the next tick sequentially, not one per tick. This is the same mechanism used bythenAsync
to queue the callbacks for the next tick, but is also a handy helper for more optimized defering of multiple callbacks to the next tick.fromThenable(thenable)
- A handy helper function to wrap any sort ofThenable
(usually used for wrapping ES6 Promises) into a SyncTask. This takes the thenable and maps its success and failure cases into a newSyncTasks.Promise
that resolve and reject with the results of the passed thenable.setTracingEnabled(boolean)
- This option allows enabling of double resolution tracing individually per Promise. Could be used in the release in cases then the problem couldn't be reproduced locally. If option enabled assert will give you two stack traces - for the first resolve and the second. By default, you would see only second resolve stack trace. Keep in mind that it adds an extra overhead as resolve method call will create an extra Error object, so it should be used with caution in the release. Estimated overhead on mobile is around 0.05ms per resolve/reject call on Nexus 5x android.
SyncTasks.Deferred Reference
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 the success chain of any of the promises.reject(obj?)
- Resolves any promises created by the deferral with failure. Takes an optional value that is passed through the success chain of any of the promises.onCancel(callback)
- Adds a cancellation callback function that is called whenever non-resolved/rejected promises created by the deferral get.cancel
called on them (or called on chained promises chain back up to this one.) This callback can be used to handle aborting async tasks that return promises.promise()
- Returns aSyncTasks.Promise
object from the deferral, which is then passed around the world.
SyncTasks.Promise Reference
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 successfully resolves, it will call the first callback (and will pass it the optional resolution value). If it resolves with failure, the second callback is called (also with an optional resolution value). From inside each callback, you are able to return either a value, which will then turn the resolution into a successful resolution and change the resolution value to the value you just returned, or you may also return a new promise, which then continues the resolution chaining process until something returns only a value (or undefined). Returning anything from either the successCallback or failureCallback functions will chain to success of any subsequent promises. Thethen
call returns a new promise which will be resolved based on the resolution chain of the callbacks from this call.thenAsync(successCallback?, failureCallback?)
- Has the same nuances and behavior asthen
, but the callbacks are called asynchronously, in ES6 fashion, on the next tick of the javascript host engine.always(callback)
- A synonym for callingthen
with the same callback for both parameters. Use this when you want to always alter the resolution chain, regardless of whether it came in with success or failure. Just likethen
, returns a new promise for chaining.catch(callback)
- Has the same effect as callingthen
with the specified callback for the failureCallback parameter and undefined for the successCallback -- this will only call your callback in the event of a chained failure case, but anything returned from your callback here will chain future callbacks to success. Just likethen
, returns a new promise for chaining.done(callback)
,fail(callback)
, andfinally(callback)
- If you would like to observe, but not change, the resolution chain, you can use these functions. In all three cases, nothing you return from the callback functions will have any effect, these are only for observing the resolution chain. The only difference between them is thatdone
is only called if the resolution chain is successful,fail
is only called if the resolution chain is failure, andfinally
is called in either case. The callback function is always passed the optional resolution value, but infinally
's case, you have no idea whether it was called based on success or failure. These three functions all return the same original promise object, so you can attach multiple "observation functions" to the same promise without having to store it in a temporary variable.cancel(obj?)
- This method will notify the original deferral object of cancellation, and will pass it the optional value it is called with, but has no further effects. If the deferral is not handling cancellation, then this call will do absolutely nothing -- it does not guarantee any effects like failure resolution or any sort of stopping of chaining. The cancellation attempt will walk backwards as far up the promise chain as possible, so if you cancel a promise, be aware that it may end up calling cancel functions for deferrals many steps back in the promise chain.toEs6Promise()
- A helper function that wrapsSyncTasks.Promise
object "back" into ES6 Promise. It directly maps success and failure cases into respective calls to ES6 Promise constructor argumentsresolve
andreject
Examples
Simple Usage
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".
Add Cancellation
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.