JavaScript library to express concurrency patterns.
Getjs is a control flow library based on generators to simplify the development of concurrent solutions for JavaScript. It works in both nodejs and the browser, allowing you to deal with the JavaScript asynchronous nature by writing sequential code.
Getjs implements Communicating Sequential Process(CSP) by emulating Golang concurrency primitives as much as possible with a few deviations to fit in the JavaScript ecosystem. It works with every library based on Promises.
Pingpong example (ported from Go)
var player = get(function*(name, table) {
var ball
while (true) {
ball = yield get(table)
if (table.closed) {
console.log(name, 'table is gone')
return
}
ball.hits += 1
console.log(name, ball.hits)
yield get.timeout(100)
if (table.opened) {
yield get.send(table, ball)
}
}
})
get.go(function*() {
var
table = get.chan()
player('Ping', table)
player('Pong', table)
yield get.send(table, {hits: 0})
yield get.timeout(1000)
get.close(table)
})
- Installation
- Coroutines
- Channels
- Parallel Resolution
- Race Resolution
- Pausing Coroutines
- Promisifying
- Idiomatic Getjs
- Debugging
Browser
<script src="get.js"></script>
Nodejs
var get = require('getjs')
Coroutines are functions that run asynchronously. The body of coroutines are generator functions. Each time a promise is yielded inside a coroutine it blocks until the promise is resolved or rejected. Each coroutine execution returns a promise that is resolved when the coroutine returns, or rejected if an error occurs.
Spawning a coroutine.
get.go(function *() {
console.log('executed')
})
// with arguments
get.go(function *(name, age) {
console.log(name, age)
}, ['john', 30])
In many occasions you may need to declare coroutines to spawn on demand.
var worker = get.wrap(function *(time) {
console.log(time)
})
worker(100)
worker(500)
Waiting for a promise.
get.go(function *() {
var n = yield Promise.resolve(1)
})
Promise flow.
get.go(function *() {
return 5
}).then(function(v) {
console.log(v)
}).catch(function(e) {
console.log(e.stack)
})
Channels are structures used to communicate and synchronize coroutines. The behavior is exactly like in the Go language.
Channels can be buffered or unbuffered. When sending data through unbuffered channels it always blocks the sender until some other process receives. Once the data has been received, the sender will be unblocked and the receptor will be blocked until new data is received. Unbuffered channels are also known as synchronic channels.
// create new channel
var ch = get.chan()
get.go(function *() {
// send 'message' to a `ch`
yield get.send(ch, 'message')
// close the channel
get.close(ch)
})
get.go(function *() {
// receive from `ch`
var msg = yield get.recv(ch)
console.log(msg)
})
get.send
andget.recv
operations must preceded by theyield
keyword.
When some data is sent to a buffered channel it only blocks the coroutine if the buffer is full. The receiver only blocks if there is no data in the buffer.
var bufferSize = 20
var ch = get.chan(bufferSize)
Values passed through channels can be tranformed before being delivered.
function trans(x) {
return x*2
}
// provide a transformer
var ch = chan(null, trans)
Channels can be closed using get.close
function. Sending to a closed channel will throw an error. ch.closed
and ch.opened
allows knowing whether a channel is closed or not.
while(ch.opened) {
yield get.send(ch)
}
// close it somewhere in your code
get.close(ch)
You can wait for many tasks executed in parallel by using get.all
.
// proding an array of promises
var result = yield get.all([
$.get('http://api.com/books'),
$.get('http://api.com/authors')
]);
var books = result[0];
var authors = result[1];
You can cast by keys by using objects.
// roviding an object
var result = yield get.all({
books: $.get('http://api.com/books'),
authors: $.get('http://api.com/authors')
});
var books = result.books;
var authors = result.authors;
get.race
returns a promise that resolves once one its tasks has been resolved. The returned promise resolves with an object of the format {which: key, value: value}
.
get.go(function *() {
var result = yield get.race([$.get('http://api.com/books'), timeout(500)])
// found books
if (result.which === 0) {
var books = result.value;
} else {
// timed out
}
})
Also support objects.
get.go(function *() {
var result = yield get.race({
books : $.get('http://api.com/books'),
timeout : timeout(500)
})
// found books
if (result.which === 'books') {
var books = result.value;
} else {
// timed out
}
})
Some times you want to block a couroutine for a span of time.
// stop by 20 milliseconds
yield get.timeout(20)
It is possible to adapt a callback-based API to be used with Getjs.
var fs = require('fs')
// make the callback-based function returns a promise
var stat = get.promisify(fs.stat)
get.go(function *() {
var stats = yield get(stat('path/to/file'))
console.log(stats)
})
Also you can promisify the entire module.
var fs = get.promisify(require('fs'))
get.go(function *() {
var stats = yield fs.stat('path/to/file')
})
The get
function is overloaded, making it possible to write more succinct code. If the first parameter is a generator function it will relay on get.wrap
else it will try to convert the value to a promise through get.recv
if a channel, or get.all
if an object or array is provided.
// wrapped coroutine
func = get(function *() {})
// receive from a channel
yield get(channel)
// parallel resolution
yield get([a, b, c])
Coroutine errors are easy to handle because the promise catch
function. During development all coroutine errors are logged to the console. For production you should avoid this behaviour by setting get.debug
to false
.
get.debug = false
(c) 2016 Yosbel Marín