good-job
v1.1.1
Published
A simple way to write complex asynchronous code.
Downloads
65
Readme
Good Job
A simple way to write complex asynchronous code.
Good Job is a Node.js module that lets you glue interdependent asynchronous calls (Tasks) into complex Jobs. These Jobs can then be executed at once, converted to functions, extended and combined.
Good Job goes the extra mile by providing functions and options for error handling, Task logging and Job timeouts. Good Job makes it easier to find bugs, recover from unexpected situations and notice code that needs refactoring.
Although not tested, Good Job should run on any JavaScript platform that provides a synchronous require(module_name)
function.
Quick Example
/// It makes sense to use a very short name for good-job variable.
/// "X" is used as a convention in this documentation.
var X = require("good-job");
X.run({
file_name: "/etc/hosts",
contents: X.call("fs#readFile", X.get("file_name"), "base64")
.onError(console.error),
output: X.callSync(console.log, X.get("contents")),
});
Download
Install using Node Package Manager (npm):
$ npm install good-job
Download from GitHub:
$ git clone git://github.com/emilis/good-job.git
Or get the zipped version here.
Documentation
All of the functions exported by good-job (except options()) create a new Job, Task or Request and call their own methods. You can then use method chaining to extend them:
/// Create a new Task, specify its callback and error handler:
X.wait("check_permission")
.call(fs.readFile, X.get("file_name"))
.onError(console.error, "Failed to read the file.");
/// Create a new Request that will transform its result:
X.get("file_contents")
.apply(function(contents){
return contents.toLowerCase();
});
Exports and methods:
good-job
options(obj)
Utility function: wraps a given object as an Options object so that it won't be mixed up with a task list by a Job.
Arguments
- obj - Key-value options for a Job.
Job
A Job manages asynchronous tasks: tracks their dependencies, executes them when possible, logs their errors. A Job is a dictionary with task names for keys and tasks (or their results) for values.
Job.extend(...)extend(...)
Adds more tasks for the Job, sets callback or error handler and options. Returns the Job.
Arguments
Any number of the following:
- Object - Specifies a task list. Keys for task names, values for Tasks (see Job.addTasks).
- Options - Sets options for the Job. You can create Options with X.options.
- Function - Sets callback for the Job. If the callback is already set, sets the error handler (see Job.setCallback, Job.setErrback).
Example
X.extend(
X.options({
id: "MyJob",
log: true,
}),
{
task1: ...,
task2: ...,
},
function(err, result) {
/// This will be called when the job finishes successfully.
/// This would also be called if the job had no error handler.
},
function(err, result) {
/// This will be called when a task emits an error.
});
Job.addTasks(task_list)tasks(task_list)
Adds more tasks for the Job. Returns the Job.
Arguments
- task_list - An Object with keys for task names and values for Tasks. Requests get converted to Tasks. Other values are marked as finished tasks for the Job.
Example
myJob.addTasks({
file_name: "/etc/hosts",
contents: X.call(fs.readFile, X.get("file_name"), "ascii"),
print: X.call(console.log, X.get("contents")),
});
Job.compile(arg_names, ...)createFunction(arg_names, ...)
Returns an asynchronous function that runs the Job with the given arguments used as finished task results.
Arguments
- arg_names - An Array specifying how function arguments will be named inside the Job. E.g.
["file_name", "mode"]
. - Further arguments extend the Job before creating the function.
Example
Create a function that joins contents of two files:
/// You could use "job.compile(["f1_name","f2_name])" on an existing job.
var cat2files = X.createFunction(
["f1_name", "f2_name"],
{
f1_contents: X.call(fs.readFile, X.get("f1_name"), "utf8"),
f2_contents: X.call(fs.readFile, X.get("f2_name"), "utf8"),
cat: X.get(["f1_contents","f2_contents"])
.apply(function(c1, c2){
return [c1, c2].join("\n");
}),
});
/// We can now use the function. Note that it is asynchronous:
cat2files("/etc/hosts", "/etc/passwd", console.log);
Job.run(...)run(...)
Executes the Job.
Arguments
- If there are any, the Job is extended before running.
Job.clone()
Returns a clone of the Job object. Needed when executing the Job more than once, because Jobs save their execution state. See also Job.compile.
Job.setCallback(cb)
Sets a function to call when the Job finishes. If the Job has an error handler, this function will only be called on success. Returns the Job.
Note that if a task uses Task.exitOnError, this Job callback won't be called.
Arguments
- cb -
function(err, result) { ... }
Job.setErrback(cb)
Sets a function to call when the Job finishes with error - a Task returns an error or a timeout occurs. Returns the Job.
Note that if a task uses Task.exitOnError, this Job error handler won't be called.
- cb -
function(err, result) { ... }
Job.options(obj)
Overwrites the current Job options. Returns the Job.
Supported Options
- id - ID for the job that is used for logging.
- log -
true
to turn on logging. - timeout - Number of miliseconds after which the Job is stopped if not finished.
Example
X.run(
X.options({
id: module.id,
log: true,
timeout: 2000, // 2 seconds
}),
{
t1: X.callSync(console.log, new Date()),
t2: X.call(function(cb){
/// This task will cause the Job to timeout:
setTimeout(cb, 3000, null, "Long task result");
}),
t3: X.callSync(console.log, X.get("t2")), // This task will not run
});
Job.log(msg_type, ...)
Prints a log message for the Job.
Arguments
- msg_type, - One of: INFO, EXEC, VALUE, FUNC, DONE, LATE, WARN, ERROR, TIMEOUT, DEBUG.
- Further arguments get printed as they are.
Job.logDone(name, err, result)
Prints a log message for a Task or Job that is done.
Arguments
- name - Job or Task name.
- err, result - Arguments passed to Task or Job callback (only their types are printed).
Task
A Task is a wrapper around a function that exposes its dependencies for the Job. It handles error results, catches exceptions.
Task.wait(...)wait(...)
Explicitely adds dependencies for the Task. Returns the Task.
Arguments
- Names (Strings) of other tasks in the Job.
Example
X.wait("t1", "t2", "t3")...
Task.call(fn, ...)call(fn, ...)
Wraps an asynchronous function. Returns the Task.
Arguments
- fn - A function or a function URI. Note that functions will lose their context (
this
value) unless bound. The function has to accept callback as its last argument. - Further values will be passed as arguments to the function when it is called. Request values will be added to the Task dependencies and resolved when the function is called.
Example
/// Note that this only creates the Task and does not execute it:
X.call("fs#readFile", "/etc/hosts", "utf8");
Task.callSync(fn, ...)callSync(fn, ...)
Wraps a synchronous function. Returns the Task.
Arguments
- fn - A function or a function URI. Note that functions will lose their context (
this
value) unless bound. The function has to return its result. - Further values will be passed as arguments to the function when it is called. Request values will be added to the Task dependencies and resolved when the function is called.
Example
/// Note that this only creates the Task and does not execute it:
X.callSync("fs#readFileSync", "/etc/hosts", "utf8");
Task.subTasks(result_map, tasks)subTasks(result_map, tasks)
Adds a Job as a Task. Returns the Task.
Arguments
- result_map - An Array of dependency names or an Object with keys for parent Job task names and values for child Job task names.
- tasks - A task list. See Job.addTasks.
Example
X.run({
file_name: "/etc/hosts",
file: X.subTasks({"file_name":"name"},
{
contents: X.call("fs#readFile", X.get("name"), "utf8"),
stat: X.call("fs#stat", X.get("name")),
}),
print: X.callSync(console.log, X.get("file.name"), X.get("file.stat.ctime")),
});
Task.onError(fn, ...)
Sets an error handler for the Task. This error handler is executed when the Task returns an error or throws an exception. Returns the Task.
Job callback/error handler will be called after this function is called.
Arguments
- fn - A function or a function URI. The function has to accept
err,result
as its last arguments. - Further values will be passed as arguments to the function when it is called. Request values will be added to the Task dependencies and resolved when the function is called.
Example
X.run({
t1: X.call("fs#readFile", "does-not-exist", "ascii")
.onError(console.error, "The file did not exist!"),
},
function(err, result) {
console.log("Job result", err, result);
});
Task.onError(fn, ...)
Sets an error handler for the Task. This error handler is executed when the Task returns an error or throws an exception. Returns the Task.
Job callback/error handler will not be called if this function is called.
Arguments
- fn - A function or a function URI. The function has to accept
err,result
as its last arguments. - Further values will be passed as arguments to the function when it is called. Request values will be added to the Task dependencies and resolved when the function is called.
Example
X.run({
t1: X.call("fs#readFile", "does-not-exist", "ascii")
.exitOnError(console.error, "The file did not exist!"),
},
function(err, result) {
/// This will not be called if t1 fails.
console.log("Job result", err, result);
});
Task.compile()
Creates and returns a function from the Task. The function accepts callback as its first argument and an object holding requested dependencies as its second argument.
Example
var getFileContents = X.call("fs#readFile", X.get("file_name"), "utf8").compile();
getFileContents(console.log, {file_name:"/etc/hosts"});
Request
A Request specifies a Task dependency on a result from some other Task and provides ways to transform this result before using. Requests can also be used as Tasks in a Job.
Request.get(query)get(query)
Specifies what value to extract from Job results (when it will be needed). Returns the Request.
Arguments
- query - A String with the name of a Task or a String with the name of an expected property of a Task (e.g. "t1.prop1.prop2"), or an Array of such strings.
Example
X.get("env.SHELL").getValue(process);
Request.getAll()getAll()
Used as a placeholder for all Job results. Returns the Request.
Example
X.getAll().getValue({a:42});
Request.apply(fn)
Sets a filter function to use when getting value from results. Returns the Request.
Arguments
- fn - A function that takes results from queries as arguments.
Example
/// These should print the commands to list your home directory:
X.get("HOME")
.apply(function(home){
return "ls " + home;
})
.getValue(process.env);
X.get(["SHELL","HOME"])
.apply(function(shell, home){
return shell+' -c "ls '+home+'"';
})
.getValue(process.env);
X.getAll()
.apply(function(env){
return "ls " + env.HOME;
})
.getValue(process.env);
Request.getValue(results)
Resolves this Request for the given Job results and returns the requested value.
Arguments
- results - An Object with keys for Task names and values for their results.
Example
X.get("a").getValue({a:42});
Request.toTask()
Converts the Request to a Task that just returns the requested value when possible. Returns the Task.
Example
/// See also Task.compile().
var returnAProperty = X.get("a").toTask().compile();
returnAProperty(console.log, {a:42});
Request.getDependencies()
Returns an Array of names of Tasks that this Request needs to finish before getting the value.
Example
X.get("a.b.c").getDependencies();
Other
Function URIs
Function URIs are Strings representing a function that can be loaded from some external module.
URIs consist of a path and a fragment separated by "#". "path#fragment"
is used as require("path")["fragment"]
when compiling a Task.
Task.call, Task.callSync, Task.onError, Task.exitOnError accept URIs in place of functions.
Example
/// These are equivalent:
var fs = require("fs");
X.call(fs.readFile, "/etc/hosts", "utf8");
X.call("fs#readFile", "/etc/hosts", "utf8");
About Good Job
Copyright and License
Copyright 2012 UAB PriceOn http://www.priceon.lt/.
This is free software, and you are welcome to redistribute it under certain conditions; see LICENSE.txt for details.
Author
Emilis Dambauskas [email protected], http://emilis.github.com/.