Righto
Wana do some async stuff? Righto..
What
make caching, dependency resolving tasks
righto
takes a task to run, and arguments to pass to the task. If you pass a righto
'd task as an argument, it will be resolved before running the dependant task.
righto(task, [argument or righto task])
righto
'd tasks are resolved once and the result is cached. If a task is in flight when it's results are asked for, the results will be passed when the task resolves.
Who's using it?
Used in the backend of https://7tennis.com.au/, which handled 800k concurrent users for the early 2017 season.
Used everywhere in the backend and frontend of https://securetenant.com.au/
example:
async dependencies passed to bar:
function foo(callback){
setTimeout(function(){
callback(null, 'world');
}, 1000);
}
var getFoo = righto(foo);
function bar(a, callback){
callback(null, 'hello ' + a);
}
var getBar = righto(bar, getFoo);
getBar(function(error, result){
result -> 'hello world';
});
API support
Callbacks
righto suports passing error-first CPS functions by default as tasks:
function foo(callback){
setTimeout(function(){
callback(null, 'foo');
});
}
var eventuallyFoo = righto(getFoo);
eventuallyFoo(function(error, result){
result === 'foo';
});
Promise
righto supports passing Promises as a dependency:
var somePromise = new Promise(function(resolve, reject){
setTimeout(function(){
resolve('foo');
});
});
var someRighto = righto(function(somePromiseResult, done){
done(null, somePromiseResult);
}, somePromise);
someRighto(function(error, result){
result === 'foo';
});
Generators (yield)
righto supports running a generator (or any next
able iterator):
var generated = righto.iterate(function*(){
var x = yield righto(function(done){
setTimeout(function(){
done(null, 'x');
});
});
var y = yield righto(function(done){
setTimeout(function(){
done(null, 'y');
});
});
return x + y;
});
generated(function(error, result){
result === 'xy';
});
Errors
Errors bubble up through tasks, so if a dependency errors, the task errors.
function foo(callback){
setTimeout(function(){
callback(new Error('IT BORKED!'));
}, 1000);
}
var getFoo = righto(foo);
function bar(a, callback){
callback(null, 'hello ' + a);
}
var getBar = righto(bar, getFoo);
getBar(function(error, result){
error -> IT BORKED!;
});
Immediately execute
You can force a righto task for run at any time without dealing with the results (or error) by calling
it with no arguments:
// Lazily resolve (won't run untill called)
var something = righto(getSomething);
// Force something to start resolving *now*
something();
// later ...
something(function(error, result){
// handle error or use result.
});
Also, since righto tasks return themselves when called, you can do this a little more shorthand, like so:
// Immediately force the righto to begin resolving.
var something = righto(getSomething)(); // <= note the call with no arguments.
// later ...
something(function(error, result){
// handle error or use result.
});
Take / Multiple results
By default, dependent tasks are passed only the first result of a dependency righto
. eg:
function foo(callback){
setTimeout(function(){
callback(null, 'first', 'second', 'third');
}, 1000);
}
var getFoo = righto(foo);
function bar(a, callback){
callback(null, a);
}
var getBar = righto(bar, getFoo);
getBar(function(error, result){
result -> 'first';
});
But you can pick and choose what results are used from a dependency like so:
var getBar = righto(bar, righto.take(getFoo, 0, 2));
getBar(function(error, result){
result -> 'first third';
});
Reduce
righto.reduce takes N tasks, or an Array of tasks as the first argument,
resolves them from left-to-right, optionally passing the result of the last, and the next task to a reducer.
If no reducer is passed, the tasks will be resolved in series, and the final tasks result will be passed as the result from reduce.
If a reducer is used, a seed can optionally be passed as the third parameter.
If no tasks are passed, the final result will be undefined
.
No reducer passed:
function a(callback){
aCalled = true;
t.pass('a called');
callback(null, 1);
}
function b(callback){
t.ok(aCalled, 'b called after a');
callback(null, 2);
}
var result = righto.reduce([a, b]);
result(function(error, finalResult){
});
With a custom reducer, and seed.
function a(last, callback){
aCalled = true;
t.pass('a called');
callback(null, last);
}
function b(last, callback){
t.ok(aCalled, 'b called after a');
callback(null, last + 2);
}
var result = righto.reduce(
[a, b],
function(result, next){
return righto(next, result);
},
5
);
result(function(error, finalResult){
});
After
Sometimes you need a task to run after another has succeeded, but you don't need its results,
righto.after(task1, task2, taskN...) can be used to achieve this:
function foo(callback){
setTimeout(function(){
callback(null, 'first result');
}, 1000);
}
var getFoo = righto(foo);
function bar(callback){
callback(null, 'second result');
}
var getBar = righto(bar, righto.after(getFoo));
getBar(function(error, result){
result -> 'second result';
});
All
righto.all takes N tasks, or an Array of tasks as the first argument, resolves them all
in parallel, and results in an Array of results.
var task1 = righto(function(done){
setTimeout(function(){
done(null, 'a');
}, 1000);
});
var task2 = righto(function(done){
setTimeout(function(){
done(null, 'b');
}, 1000);
});
var task3 = righto(function(done){
setTimeout(function(){
done(null, 'c');
}, 1000);
});
var all = righto.all([task1, task2, task3]);
all(function(error, results){
results;
});
Sync
Synchronous functions can be used to create righto tasks using righto.sync:
var someNumber = righto(function(done){
setTimeout(function(){
done(null, 5);
}, 1000);
}
function addFive(value){
return value + 5;
}
var syncTask = righto.sync(addFive, someNumber);
syncTask(function(error, result){
result;
});
Eventuals can also be returned from inside righto.sync, which will be resolved within the flow.
From
Anything can be converted to a righto with righto.from(anything);
righto.from(someRighto);
righto.from(somePromise);
righto.from(5);
righto.from(createARighto, args...);
righto.from(createAPromise, args...);
Value (passing resolvables as unresolved arguments)
Sometimes it may be required to pass a resolvable (a righto, or promise) without as an argument,
rather than passing the resolved value of the resolvable. you can do this using righto.value(resolvable)
var righto1 = righto(function(done){
done(null, 5);
});
var rightoValue = righto.value(righto1);
var righto2 = righto(function(value, done){
value(function(error, x){
});
}, rightoValue);
righto2();
Get
You can create a new righto
that resolves the key/runs a function on a result like so:
var user = righto(getUser);
var userName = user.get('name');
var userName = user.get(user => user.name);
userName(function(error, name){
});
And keys can be righto
's as well:
var user = righto(getUser);
var userKey = righto(getKey);
var userName = user.get(userKey);
userName(function(error, something){
});
Surely (resolve [error?, results...?])
You can resolve a task to an array containing either
the error or results from a righto with righto.surely
var errorOrX = righto.surely(function(done){
done(new Error('borked'));
});
var errorOrY = righto.surely(function(done){
done(null, 'y');
});
var z = righto(function([xError, x], [yError, y]){
xError;
x;
yError;
y;
}, errorOrX, errorOrY);
z();
Handle
Wrap a righto task with a handler that either forwards the successful result, or
sends the rejected error through a handler to resolve the task.
function mightFail(callback){
if(Math.random() > 0.5){
callback('borked');
}else{
callback(null, 'result');
}
};
function defaultString(error, callback){
callback(null, '');
}
var maybeAString = righto(mightFail),
aString = righto.handle(maybeAString, defaultString);
aString(function(error, result){
typeof result === 'string';
});
This can also be used to pass custom error results:
function nullOnNoent(error, callback){
if(error.code === 'ENOENT'){
return callback();
}
return callback(error);
}
var aFile = righto(fs.readFile, 'someFilePath.txt', 'utf8'),
aFileOrNull = righto.handle(aFile, nullOnNoent);
aFile(function(error, result){
});
Possible rightos: righto.from(anything)
Any value can be turned into a righto using righto.from();
var num = righto.from(1);
var string = righto.from('hello');
var nothing = righto.from(null);
var anyValue = righto.from(anything);
var self = righto.from(someRighto);
Resolve
Resolves an object to a new object where any righto values are resolved:
var foo = righto(function(callback){
asyncify(function(){
callback(null, 'foo');
});
});
var bar = righto.resolve({foo: {bar: foo}}, true);
bar(function(error, bar){
bar;
});
Mate
Occasionally you might want to mate a number of tasks into one task.
For this, you can use righto.mate.
function getStuff(callback){
callback(null, 3);
}
var stuff = righto(getStuff);
function getOtherStuff(callback){
callback(null, 7);
}
var otherStuff = righto(getOtherStuff);
var stuffAndOtherStuff = righto.mate(stuff, otherStuff);
stuffAndOtherStuff(function(error, stuff, otherStuff){
error -> null
stuff -> 3
otherStuff -> 7
});
Fail
A shorthand way to provide a failed result.
This is handy for rejecting in .get methods.
var something = someRighto.get(function(value){
if(!value){
return righto.fail('was falsey');
}
return value;
});
Proxy support
If you are using righto in an environment that supports proxies, you can use the proxy API:
var righto = require('righto').proxy;
var foo = righto(function(done){
setTimeout(function(){
done(null, {foo: 'bar'});
});
});
foo.bar(function(error, bar){
bar === 'bar'
});
The proxied api always returns the proxied version, meaning you can dot-access arbitrarily:
var righto = require('righto').proxy;
var foo = righto(function(done){
setTimeout(function(){
done(null, {
foo: {
bar: {
baz: 'hello'
}
}
});
});
});
foo.bar.baz(function(error, baz){
baz === 'hello'
});
isRighto, isThenable, isResolvable
Use these methods to check if something is a righto, a thenable, or resolvable (either righto or thenable);
var rigthoA = righto(function(done){
done(null, 1);
});
var promiseB = new Promise(function(resolve, reject){
resolve(null, 2);
});
righto.isRighto(rigthoA); -> true
righto.isRighto(promiseB); -> false
righto.isThenable(rigthoA); -> false
righto.isThenable(promiseB); -> true
righto.isResolvable(rigthoA); -> true
righto.isResolvable(promiseB); -> true
Tracing
You can get a trace of where a righto went to resolve its dependencies by setting:
righto._debug = true;
which will tell any following calls to righto
to store a stack trace against it.
You can then retrieve the trace with:
var x = righto(something, ...);
x._trace(); ->
something (.../index.js:1034:13)
- argument "b" from taskB (.../index.js:1022:13)
- argument "a" from taskA (.../index.js:1016:13)
- argument "c" from taskC (.../index.js:1028:13)
- argument "a" from taskA (.../index.js:1016:13)
- argument "b" from taskB (.../index.js:1022:13)
- argument "a" from taskA (.../index.js:1016:13)
You can also tell righto to print a graph trace, highlighting the offending task, when a graph rejects.
Either per-righto:
var task = righto(fn, dep, dep, dep...);
task._traceOnError = true;
task();
Or globally:
righto._autotraceOnError = true;
Which print handy traces like this one:
NOTE:
Only rightos that were instantiated after setting the debug flag will support tracing.
Unsupported from v1.
The take/ignore syntax that used a single righto within an array has been deprecated
due to it having been a bad idea that I should never have implemented to begin with.
This syntax was deprecated a few v1 versions ago, so you can get a console.warn for potential usages
of the syntax by turning on _debug mode and _warnOnUnsupported:
righto._debug = true;
righto._warnOnUnsupported = true;
var getFoo = righto(foo, [getBar]);
getFoo( ... );