This package has been deprecated

Author message:

you should switch to async/await if possible

coastline

5.1.3 • Public • Published

Coastline

Deprecated. Async/await has infinitely better support than when this project was started and restarted and we've switched to promises in all new projects some time ago.

Some of the features are not implementable with promises (env, deadlocks, etc) but that's not a reason to be incompatible with the rest of the world.


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).

Features

  • "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

Usage

In Node.js

Just npm install coastline and then var CL = require('coastline');.

In browser

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.

In Node.js/Browserify modules

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.

Interactive examples

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.

  1. Simple example
  2. CL.bg() example
  3. CL.each() & CL.mapParallel() example
  4. Host objects and error handling example
  5. Deadlock example
  6. Retry example

API documentation

Note: there are many ways to pass code to Coastline. In this documentation:

  • Function specifically means unwrapped function or generator function.
  • Method means one of:
    • a function
    • a generator function (function* () {})
    • a CL.fun() wrapped function
    • an array [object, 'methodName'] (since 3.0.0) (cannot be yielded)
    • an array [object, Method] (since 3.0.0) (cannot be yielded)
  • Task means one of:
    • a Method
    • a generator (result of running a function*)
    • a Promises/A+ object.
  • Context is the object returned by CL.* methods which can be passed to other methods or 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.

CL.run(Method, arguments...)

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);

yield Task

  • Starts the Task if it's not started yet and waits for it to finish, returning the result.
  • Waits for the result of CL.bg() or unrelated Contexts.
  • If CL.cb() and similar were called, returns the result of the created context, regardless of what's actually yielded.
  • Otherwise passes anything that is not a Task or a Context through without modification.

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

return Context|Value|result

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());

CL.immediate(Context)

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); }));

CL.val(value)

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;

CL.fun(Function, [options])

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 used
  • loose - 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.

CL.top(Method, arguments...)

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.

CL.spawn(Method, arguments...)

Starts a new caller chain, same as CL.top(), but retains the environment created by CL.set() or CL.env().

yield CL.visit(Object, Method, arguments...)

Appends the task to the object's host context. The remaining arguments are the same as for CL.run()

CL.bg(Task | Context, args...)

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.

CL.cb(errorType)

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.

CL.done()

Same as CL.cb() but creates a callback(result) and does not handle errors.

yield setTimeout(CL.done(), 1000);

CL.error(errorType)

Same as CL.cb() but creates a callback(error).

img.onload = CL.done();
img.onerror = CL.error();
img.src = 'img.jpg';
yield;

CL.throw(type, error)

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:

  • string type, string error message (type will be assigned to Error's .type)
  • string type, standard Error object
  • string type, any Object (will be assigned to .data)
  • string - will be both .type and .message
  • Error object
  • any Object

Example:

CL.throw('CustomError', 'A custom error has occured');
CL.throw('CustomError', { useful: 'information' });
yield CL.try(..., function* (e) { CL.throw(e); });

yield CL.try(Method, Method | { errorType: Method })

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:

  • A catch-all Method
  • An object where key is error type and value is handler. Key * 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.

CL.onError = function (e) { ... }

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.

CL.tag(tag)

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');

CL.priority(prio)

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.

CL.set(key, value)

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...');

CL.get(key)

Get a value by key previously defined by CL.set() on current context or one of its callers.

CL.env(data, Method|Context, args...)

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.

yield CL.wait(Array | Object)

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.

yield CL.resolve(Array | Object)

Deep version of CL.wait(). Recursively waits on any Contexts found in an array or object.

yield CL.sleep(ms);

Suspend the context for a said number of milliseconds.

console.log('Your call is very important to us, please hold.');
yield CL.sleep(10000);

CL.expect()

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);

yield CL.each(Array | Object, Method)

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)

yield CL.map(Array | Object, Method)

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 ]

yield CL.parallel(Array | Object, Method, limit)

  • run Method(element, i) over all elements of array in parallel.
  • run 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.

yield CL.mapParallel(Array | Object, Method, limit)

Same as CL.parallel() but returns an Array or Object with function's return values.

CL.queue(options)

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 default
  • sleep number of milliseconds to sleep between tasks, 0 by default

Queue.push(Task, args...)

Pushes 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 yielded 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');

Potentially private methods

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()

Debugging

CL.debug(true|false)

Turn the debugging features on or off. Note that this may significantly degrade performance.

This currently enables

  • a list of active contexts
  • stack traces

CL.getActive()

Get a list of Contexts that have started but haven't finished yet. Requires CL.debug(true).

Context.trace()

Get a stack trace of all contexts leading to the current one.

console.log(CL.cur().trace());

CLEnableDebugging

A window or global field that can be set to initialize CL.debug() to said value when Coastline loads.

Changelog

5.1.2

5.1.1

5.0.1

5.0.0

  • Host contexts will now remove themselves from objects upon finishing the queue to release resources. This means that there now can be more than one host context in object's lifetime. A very low possibility of things breaking exists.
  • all commits for this version

4.4.1

4.4.0

4.3.0

4.2.3

4.2.2

4.2.1

4.2.0

  • Moved get/set methods to Context so they can now be called on any context, not just current one.
  • Not assuming there was no value in Context before method return - allows to preset return value manually.
  • all commits for this version

4.1.0

  • Implemented asynchronous stack traces. These will be printed automatically on error and can be obtained manually by .trace(). Note that CL.debug(true) now significantly decreases performance (up to 10x).
  • all commits for this version

4.0.0

  • breaking: when returning undefined (or not returning), the return value will now be undefined (as expected) and not the value of the last yield.
  • all commits for this version

3.3.0

  • minimal debugging tools introduced
  • don't crash after waiting on host contexts
  • don't crash after babel recompilation
  • all commits for this version

3.2.3

Multiple bugfix and performance releases.

  • use setImmediate where available
  • fixed get-over-10 benchmark
  • CL.sleep(0) benchmark
  • do not setTimeout all the time
  • do not consider yielded arrays Methods - this can lead to too confusing bugs
  • all commits for this version

3.2.0

3.1.0

3.0.0

  • breaking: CL.wait() will now wait on host context if passed a CL.obj() (or a host of CL.fun())
  • breaking: Method can now be an array with object as the first element and method as the second. This includes yield-ed values, so may break things.
  • CL.env() method added
  • optimizations for very long queues
  • all commits for this version

2.2.0

2.1.0

2.0.0

  • breaking: return now behaves as expected for functions, generators and promises (returns them unmodified)
  • expose wrapped function as .method in CL.fun()
  • CL.val() added
  • all commits for this version

1.1.0

  • added CL.map() and CL.mapParallel()
  • yielding unrelated contexts now fully supported
  • CL.expect()
  • various fixes
  • all commits for this version

Want to talk or have questions?

Package Sidebar

Install

npm i coastline

Weekly Downloads

3

Version

5.1.3

License

MIT

Unpacked Size

126 kB

Total Files

42

Last publish

Collaborators

  • nelaquetan
  • virtulis