Co-Executioner wraps around generator functions managing execution to regulate heavy tasks by providing multi-level, configurable pooling and retry strategies, while allowing for inline non-blocking code.
$ npm install co-executioner
$ mocha
Create an executioner
const Executioner = require('co-executioner');
executioner = new Executioner('executioner 0');
Create a task
const Task = Executioner.Task;
task = new Task('simple task', function*() {
// Do async operations
return data; // data to resolve
});
Execute the task
const process = executioner.run(task); // Returns Process
// Process implements then/catch as a native Promise
process.then((data) => {
// data returned by the generator
});
process.catch((errors) => {
// Array of errors, from every try/retry
});
You can yield a variety of object types and they will be handled accordingly.
When yielding a generator function, it is added to the process queue of the task and run until it is resolved. NOTE: If you want to actually return generator functions, you may wrap them in another object.
...
data = yield function*() { return true }; // data = true
data = yield function*() { return true }(); // data = true
data = yield function*() { yield { // data = function*
data: function*(){ return true; }
}
};
The generator function will be executed in the same way as the base generator function, under the same task, using the same configuration.
Yielding promises will return the resolved value, or throw an error when rejected.
Task objects that are yield
, will run as sub-tasks, unless another executioner is set in its configuration.
// data = data returned from anotherGenerator
let data = yield new Task('sub', function*() { yield anotherGenerator(); });
Arrays are tested to see if all elements are of one of the following types:
- Generator
- Generator Function
- Task In each of the cases, the values will be resolved using threading. Note: An executioner may have a different count of threads and cores, arrays are parallelized using the number of threads, even tasks, unless tasks have a specified executioner
let power = function*(data) { return data * data; }
...
data = yield [0..10].map(power) // data = [0, 1, 4, 9, ...]
Executioner considers values any non-aforementioned types and will return them as they are.
The number of retries and the interval between them can be specified both on an executioner and task level. Once an Error object is yield or an exception is thrown, the executioner starts over the Process after waiting for an interval set in milliseconds and adds the error to a list. When the maximum number of retries is reached, the process will reject passing the array of errors. Note that nested Tasks will implement their retries individually, meaning that a yielded Task will not add to the number of retries of the parent Task.
Configuration parameters:
retries
: Number of retries before rejecting.retryInterval
: Time to wait between failure and retry in ms.
Executioners and Tasks are configurable. For the shared parameters: Default Executioner configuration < Executioner < Task
Executioner configuration [default values
]:
name: String // Name of the executioner ['default']
retries: Number // Number of retries before failure [1]
retryInterval: Number // Interval between failure and start of retry in ms [200]
cores: Number // Task pool size [1]
threads: Number // Maximum threads per Process [1]
silent: Boolean // Mute logging [false]
pooling: Boolean // Keep pool of active tasks, throttle execution to those [true]
log: Function // Custom log function, use with silent set to false
timeout: Number // Exceeding execution time (in ms) results in task failure. [0 - disabled]
Task configuration
name: String // Name of the task ['<anon>']
retries: Number // Number of retries before failure
retryInterval: Number // Interval between failure and start of retry in ms
threads: Number // Maximum threads
heavy: Boolean // If set to true, no other tasks will cycle while this Process is running
timeout: Number // Exceeding execution time (in ms) results in task failure. [0 - disabled]
Co-Executioner comes with a set of templates to serve common usage patterns. You can easily access them as such:
{Templates} = require('co-executioner');
waiter
can be yielded and it will wait for a set amount of ms before returning.
functor
takes in an array of functions that may return yieldables. The result of the functions is returned in an ordered array. The functor allows for threading, so the functions will be pooled and executed in order and on demand. For example, if you have 2 threads and pass 10 functions to the functor, it will first execute 2 of them, and if they return a yieldable it will wait for it to resolve before executing the next function in the array.
callback
(or promisify
) takes in a node-style callback function and returns a yieldable. It will throw an error in case of non-null err.
spawn
is a shortcut of new Task(...)
.
sync
will yield
each element of the array in sequence, one-by-one, and return an ordered array of the results.
MIT