A proper call stack for asynchronous JavaScript
Homepage Repository npm JavaScript Download
npm install coastline@5.1.3
A proper call stack for asynchronous JavaScript.
Please note that Coastline only works on Node 4.0.0 and up. It relies heavily on generators (but will work with Babel/Traceur in old browsers).
"Synchronous" asynchronous operations via ES6 generators:
var value = yield (function* () { ... })();
yield CL.sleep(1000);
Single-line asynchronous method calls and waiting for events:
var buffer = yield fs.readFile('file.txt', CL.cb());
var event = yield $('button').on('click', CL.done());
Full two-way support for promises:
var result = yield new Promise(...);
CL.fun(function* () {})().then(...);
Parallel tasks via bg
:
var a = CL.bg(fs.readFile('header.html', CL.cb()));
var b = CL.bg(fs.readFile('footer.html', CL.cb()));
yield fs.writeFile('index.html', (yield a) + 'Welcome!' + (yield b), CL.cb());
Race condition control via host contexts:
var obj = { work: CL.fun(function* () { ... }) };
obj.work();
obj.work(); // executes one after the other, even when called from outside
Easy and powerful error handling:
CL.try(function* () {
fs.readFile('never.existed', CL.cb());
}, { ENOENT: function* (e) {
console.log('No surprise here.');
}});
Deadlock detection, cleanup and retrying:
CL.fun(function* () {
something++;
yield potentialDeadlock();
}, {
retry: function* () {
something--;
}
});
Storing information on stack
var query = function* () { db.query(CL.get('transaction'), ...); };
CL.set('transaction', db.begin());
yield query('SELECT...');
Fast enough - up to 10 million operations per second per core
Just npm install coastline
and then var CL = require('coastline');
.
You will probably want to compile Coastline and your code with Babel, then include browser-polyfill.js
from babel-core
package before any other code. Or if you only care about Chrome and Firefox, just import coastline.js
with a synchronous <script>
tag before your code.
Currently it is required for valid operation that there is only one Coastline object per process. Therefore Coastline will set window.CL
or global.CL
to the first module that is loaded and use that instead of any subsequent module loads. In later versions, an upgrade mechanism will be added to always use the latest version. Currently your only option is to keep your modules up to date manually.
Recommended in order. Note that JSBin Babel support seems to be broken, so these don't use it, so please view in Chrome or Firefox.
Note: there are many ways to pass code to Coastline. In this documentation:
function* () {}
)CL.fun()
wrapped function[object, 'methodName']
(since 3.0.0) (cannot be yielded)
[object, Method]
(since 3.0.0) (cannot be yielded)
function*
)yield
In this documentation, methods that have yield
before their name should be accordingly used with yield
before their call, and the ones without shouldn't.
Starts a new caller chain if the currently running code is not run by Coastline already. Can also be used to explicitly run something in current context, in a case when code executing is not a generator.
First argument is the Method. All the subsequent arguments are passed to the function.
var fun = function* (arg1, arg2) { };
CL.run(fun, arg1, arg2);
CL.bg()
or unrelated Contexts.CL.cb()
and similar were called, returns the result of the created context, regardless of what's actually yielded.Example:
123 === yield function* () { return 123 };
123 === yield (function* () { return 123 })();
123 === yield CL.bg(function* () { return 123 });
123 === yield CL.top(function* () { return 123 });
123 === yield 123;
// etc
Returns the result of the function. If a Context is returned, waits on it and returns the value of that Context instead. Use CL.val()
if you really want to return a context.
Like yield
, if CL.cb()
was called, will wait and return the result of the callback. This can't be overridden with CL.val()
.
return 123;
return CL.parallel(...);
return fs.readFile('filename', CL.cb());
Try to execute and return the result of the context immediately (out-of-band). If needed, will also fast-forward caller and host and any contexts the task yields.
Will also return a value of an already finished context. If the Context has encountered an error previously, will throw it via native throw
.
If immediate execution is not possible, will return the passed Context.
Useful e.g. to transparently wrap synchronous functions in a Context
123 === CL.immediate(CL.env({ num: 123 }, function () { return CL.get(123); }));
Creates a Value object that can be returned or yielded without evaluation. The actual received return value will be unwrapped, so you have to wrap it again if you need to pass it further. CL.wait()
or CL.resolve()
will also ignore anything wrapped in a Value.
var a = function* () {
return CL.val(CL.top(function* () { return 123; }));
};
var v = yield a();
v instanceof CL.Context == true;
yield v == 123;
Makes a Method that creates a context around the passed function and runs it. Basically, CL.fun(...)
similar to function () { return CL.run(...) }
.
The difference is that when used as an object's method this function will create and use a host context, as explained above.
Options is an optional object. Available options currently are:
retry
- can be a Method or true
and will cause the task to re-run in case of a deadlock. If it's a Method, it will be run immediately after the deadlock is detected and can be usedloose
- if set to true
, will not wait for or lock the host Context. Useful for static methods, where when calling lib.fun()
you don't want to lock lib
.Example:
Class.prototype = {
method: CL.fun(function* () {
something++;
yield potentialDeadlock();
}, {
retry: function* () {
something--;
}
});
}
The wrapped function can be accessed as the .method
property of the returned function.
Starts a new caller chain, regardless of when it's called. The arguments are the same as for CL.run()
.
CL.run(function* () {
CL.top(unrelatedMethod, arg1, arg2);
});
Note that deadlocks will not be detected if you yield
an unrelated context. This will be fixed in eventually.
Starts a new caller chain, same as CL.top()
, but retains the environment created by CL.set()
or CL.env()
.
Appends the task to the object's host context. The remaining arguments are the same as for CL.run()
Starts the Task in the background or marks the Context as a background context. Background task will not block the caller but it will wait on it before finishing.
To wait manually and receive the return value, just save the result of CL.bg()
to a variable and yield
it when you need it.
var a = CL.bg(fs.readFile('header.html', CL.cb()));
var b = CL.bg(fs.readFile('footer.html', CL.cb()));
yield fs.writeFile('index.html', (yield a) + 'Welcome!' + (yield b), CL.cb());
You can use CL.wait()
or CL.resolve()
to wait on arrays or objects containing Contexts returned by CL.bg()
.
Note: anything that involves you literally writing yield CL.bg()
will not work in parallel since it will immediately wait on the Context. So instead of func(yield CL.bg(), yield CL.bg())
you will have to use e.g. the spread operator func(...(yield CL.wait([CL.bg(), CL.bg()])))
. There may be a better way coming soon.
Creates a callback of form callback(error, result)
that can be passed to any external function. The task must yield immediately after said function is run so that this callback is bound to a correct object.
yield fs.writeFile(
'otherfile',
yield fs.readFile('filename', CL.cb()),
CL.cb()
);
If an error is received, it will be thrown as via CL.throw()
and the task will not continue. If errorType
is given, Error's type will be set to it.
Otherwise the the return value of yield
will be the result passed to the callback.
Same as CL.cb()
but creates a callback(result)
and does not handle errors.
yield setTimeout(CL.done(), 1000);
Same as CL.cb()
but creates a callback(error)
.
img.onload = CL.done();
img.onerror = CL.error();
img.src = 'img.jpg';
yield;
Throw an error up the stack. This error can be caught using CL.try()
.
If it's not caught, Coastline will terminate and CL.onError
will be run.
Can be run with 1 or 2 arguments:
type
will be assigned to Error's .type
).data
).type
and .message
Example:
CL.throw('CustomError', 'A custom error has occured');
CL.throw('CustomError', { useful: 'information' });
yield CL.try(..., function* (e) { CL.throw(e); });
Starts a task defined by first argument and catches any errors occurring anywhere down the call chain, passing them to the corresponding handler.
Handlers can be:
*
matches everything.Example:
yield CL.try(function* () {
fs.readFile('never.existed', CL.cb());
}, { ENOENT: function* (e) {
console.log('No surprise here.');
}});
Type will be either set by CL.throw()
or, if the error message contains a colon, it will be the part before the first colon. For example "ENOENT: no such file or directory" sets type to ENOENT
. If Error object contains .name
, e.g. TypeError
, that will be used instead.
CL.try()
will return the return value of the first Method on success or the return value of the second Method on failure.
Neither passing the Method, nor *
will catch Coastline errors (both internal and caused by your actions). Define handlers for CLError
and CLInternalError
if you really want to catch them. But you really don't.
A callback that is run when an unhandled error reaches the top of the stack.
By default will print the Coastline stack and throw the error, therefore crashing the process in case of Node.js. If you override it, be sure to crash the process too since the behavior of Coastline after an unhandled error is currently undefined. This may change in later versions.
The printed trace consists of tag (if present) full method code and arguments - it's the only useful information there is. Previous version would create an Error object for every Context to get a proper stack, but that's way too slow.
Refer to the code of the default handler to learn how you can use the passed Error object.
Set a custom tag for the Context that will be shown in the trace in case of unhandled error. There are no limitations on its contents.
CL.tag('Got arg ' + arg + ', calling some other function now');
Set the priority of current Context, Valid values are -5 to 5 (inclusive). Priorities are inherited from the caller. The default priority is 0.
Note that contexts with a higher priority are always executed first, regardless of how many are in the lower priority queues.
Attach data to current context that can be accessed by any child contexts. Can be used as kind of a global variable for session information etc. As it is with global variables, must be used with care.
var query = function* () { db.query(CL.get('transaction'), ...); };
CL.set('transaction', db.begin());
yield query('SELECT...');
Get a value by key previously defined by CL.set()
on current context or one of its callers.
Creates a new context with its data set to data
and optionally runs Method with args.
If passed a Context instead of Method, will set its data to data
.
Returns the created or modified Context.
Waits on multiple bg
tasks (passed as array or object) and returns said array/object with values filled in.
var results = _.each(array, function (el) {
return CL.bg(process(el));
});
results = yield CL.wait(results);
Note: CL.wait()
modifies the object it is passed. Make a copy if you need the original for some weird reason.
Deep version of CL.wait()
. Recursively waits on any Contexts found in an array or object.
Suspend the context for a said number of milliseconds.
console.log('Your call is very important to us, please hold.');
yield CL.sleep(10000);
Creates a context object that will wait until .done()
or .throw()
is called on it to finish. Yielding this is same as yielding a promise, but a bit more internal to Coastline. This can be used e.g. for request-response system:
requests[id] = CL.expect();
sendRequest(id);
var response = yield requests[id];
// elsewhere
requests[id].done(response);
Run Method(element, i)
over all elements of array or Method(value, key)
over all properties of an object, one after the other.
yield CL.each([1000, 2000, 3000], CL.sleep);
console.log('Slept for six seconds');
For non-generator functions you should use something faster (e.g. Underscore)
Run Method(element, i)
over all elements of array or Method(value, key)
over all properties of an object, one after the other and returns array or object with function's result values.
Example:
var sum = 0;
var result = yield CL.map([1, 2, 3], function* (v, i) {
return sum += v;
});
//result == [ 1, 3, 6 ]
Method(element, i)
over all elements of array in parallel.Method(value, key)
over all properties of an object in parallel.Example:
yield CL.parallel([1000, 2000, 3000], CL.sleep);
console.log('Slept for three seconds');
Execution order is not guaranteed. At most limit
tasks will be running in parallel. The default limit is 1000.
Same as CL.parallel()
but returns an Array or Object with function's return values.
Constructs a Queue
object that allows limiting parallel execution of unrelated tasks.
Available options are:
limit
maximum number of tasks to run in parallel, 1 by defaultsleep
number of milliseconds to sleep between tasks, 0 by defaultPushes a task onto the queue. If task is a method, remaining arguments will be passed to it. Returns the pushed context.
Note that tasks are not implicitly marked as bg
- tasks added from the same Context must either be yield
ed and will execute in order or must explicitly be wrapped in CL.bg()
.
var q = CL.queue({ limit: 2 });
q.push(CL.bg(CL.sleep(1000)));
yield q.push(function* (str) {
console.log(str);
}, 'hello');
The information about these methods is currently only available in the code documentation and I'd rather you didn't use them until we decide it's safe to do so. But if you find a good use case for them, please do tell me.
new CL.Context()
CL.push(Context c)
CL.obj(Object)
CL.cur()
CL.work()
Turn the debugging features on or off. Note that this may significantly degrade performance.
This currently enables
Get a list of Contexts that have started but haven't finished yet. Requires CL.debug(true)
.
Get a stack trace of all contexts leading to the current one.
console.log(CL.cur().trace());
A window
or global
field that can be set to initialize CL.debug()
to said value when Coastline loads.
compat.js
)CL.priority()
addedCL.spawn()
added.trace()
. Note that CL.debug(true)
now significantly decreases performance (up to 10x).undefined
(or not returning), the return value will now be undefined
(as expected) and not the value of the last yield
.Multiple bugfix and performance releases.
CL.wait()
will now wait on host context if passed a CL.obj()
(or a host of CL.fun()
)yield
-ed values, so may break things.Queue
class implemented.then()
calls now chainable as per Promises/A+ specloose
option addedreturn
now behaves as expected for functions, generators and promises (returns them unmodified)CL.val()
addedCL.map()
and CL.mapParallel()
CL.expect()
#coastline
on Freenode