Righto
Lazy eventuals with support for CPS, Promises, and Iterators.
Install / Use
/learn @KoryNunn/RightoREADME
An Eventuals implementation that:
- Lets you use synchronous functions, err-backs (normal callback style), promises, iterators (yield), whatever you like.
- Are lazily evaluated.
- Implicitly (and transparently) handles execution order and parallelisation.
- Provides a solution for all common (and some less common) async flows, parallel, waterfall, series, etc, etc...
- Doesn't catch thrown errors. Why is this good?
righto takes a task to run, and arguments to pass to the task. If you pass any eventual arguments (rightos or promises), they will be resolved before running the dependant 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.
QUICK REFERENCE
Signature:
righto(cps-function, ...args) -> fn(err-back)
where:
a cps-function has signature: cps-function(...args, err-back) -> void
an err-back has signature: err-back(error, ...results) -> void
Examples
Simple Example: read some files
var fs = require('fs');
function concatStrings(a, b, callback){
callback(null, a + b);
};
var myFile = righto(fs.readFile, 'utf8' 'myFile.txt');
var mySecondFile = righto(fs.readFile, 'utf8', 'mySecondFile.txt');
var concattedFiles = righto(concatStrings, myFile, mySecondFile);
concattedFiles(function(error, result){
console.log(error); // null
console.log(result); // the two concatted files.
});
A more involved example: return a document for a user while checking permissions.
// Make a task from an err-back function
var document = righto(db.Documents.get, documentId);
var user = righto(db.Users.get, userId);
// Resolve an object with eventual properties to pass to a function.
var account = righto(db.Accounts.get, righto.resolve({
userId: user.get('id')
}));
// Reject the flow if a business rule is not met
function isOwner(document, account){
if(document.ownerId !== account.id){
return righto.fail({ message: 'Account does not own document', code: 403 });
}
}
// Make a task from a synchronous function
// Depend on `document` and `account` in parallel, automatically.
var hasPermission = righto.sync(isOwner, document, account);
// Take results from a task only after another task is complete
var allowedDocument = righto.mate(document, righto.after(hasPermission));
// Use the results.
allowedDocument(function(eror, document){
// Respond with the error or the document.
});
Who's using it?
<img src="https://s.yimg.com/ao/i/mp/properties/multipass/img/plus7/channel-logo-seven.png" alt="7Tennis" height="70px"/> Used in the backend of https://7tennis.com.au/, which handled 800k concurrent users for the early 2017 season.
The inventor of JavaScript said it was cool in a tweet once: https://twitter.com/BrendanEich/status/1059210746163130368
Usage:
var eventual = righto(task, any...);
API support
Callbacks
righto suports passing error-first CPS functions by default as tasks:
function getFoo(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';
});
And supports easily integrating back to promises:
var someRighto = righto(function(done){
setTimeout(function(){
done(null, 'foo');
});
});
var somePromise = new Promise(righto.fork(someRighto));
somePromise.then(function(result){
result === 'foo';
});
Warning:
- promises execute immediately.
- promises will catch any errors thrown within their resolver, and turn them into rejections.
Righto can't help you once you pass control back to promises :)
Generators (yield)
righto supports running a generator (or any nextable iterator):
var generated = righto.iterate(function*(a, b, c){
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 + a + b + c;
});
var result = generated('a', 'b', 'c');
result(function(error, result){
result === 'xyabc';
});
Errors
Errors bubble up through tasks, so if a dependency errors, the task errors.
// Task that 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){
// ...about 1 second later...
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){
// ...1 second later...
result -> 'first';
});
But you can pick and choose what results are used from a dependency like so:
// foo() and bar() as defined above...
var getBar = righto(bar, righto.take(getFoo, 0, 2)); // Take result 0, and result 2, from getFoo
getBar(function(error, result){
// ...1 second later...
result -> 'first third';
});
Reduce
righto.reduce takes an Array of values (an an eventual that resolves to an array) 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([righto(a), righto(b)]);
result(function(error, finalResult){
// finalResult === 2
});
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);
}
// Passes previous eventual result to next reducer call.
var result = righto.reduce(
[a, b],
function(result, next){ // Reducer
return righto(next, result);
},
5 // Seed
);
result(function(error, finalResult){
// finalResult === 7
});
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)); // wait for foo before running bar.
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; // -> ['a','b','c']
});
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; // -> 10
});
Eventuals can also be returned from inside righto.sync, which will be
Related Skills
openhue
342.0kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
342.0kElevenLabs text-to-speech with mac-style say UX.
weather
342.0kGet current weather and forecasts via wttr.in or Open-Meteo
tweakcc
1.5kCustomize Claude Code's system prompts, create custom toolsets, input pattern highlighters, themes/thinking verbs/spinners, customize input box & user message styling, support AGENTS.md, unlock private/unreleased features, and much more. Supports both native/npm installs on all platforms.
