Code Monkey home page Code Monkey logo

getjs's Introduction

Getjs

badge

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

More examples

Documentation

  1. Installation
  2. Coroutines
  3. Channels
  4. Parallel Resolution
  5. Race Resolution
  6. Pausing Coroutines
  7. Promisifying
  8. Idiomatic Getjs
  9. Debugging

Installation

Browser

<script src="get.js"></script>

Nodejs

var get = require('getjs')

Coroutines

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

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 and get.recv operations must preceded by the yield 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)

Parallel Resolution

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;

Race Resolution

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

Pausing Coroutines

Some times you want to block a couroutine for a span of time.

// stop by 20 milliseconds
yield get.timeout(20)

Promisifying

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

Idiomatic Getjs

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

Debugging

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

getjs's People

Contributors

jimt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

getjs's Issues

Standardize API

Hey @yosbelms - I love what you're doing here!

I think a strength for the project, and the community would be if all the different projects had the same/standardized API.

The ultimate goal is that users can switch between libraries which will support their use-case better. Whether that is in terms of performance or new features.

Pieces that overlap with existing libraries could use the same namespace, for example csp and functions which do the same thing, could have the same name (like csp.go versus csp.run).

Converting callbacks to channels or suspenders

Node.js API uses callback approach for its async IO architecture, the next feature I'll add to Aryn is about addressing callback approach converting it to the Aryn way.

There is two proposals:

  • Channelify, convert callbacks to channels.
var fs = aryn.channelify(require('fs'))
var fileCh = fs.readFile('file.txt')

aryn.run(function*(){
    var content = yield receive(fileCh)
})
  • Suspendify, make the callback stops the runner and unblock once the task is done, or throws on error.
var fs = aryn.suspendify(require('fs'))
aryn.run(function*(){
    var content = yield receive(fs.readFile('file.txt'))
})

Both channelify and suspendify are supposed not to be used as function names in the final API, these are just for concepts explanation.

I like more the second approach for several reasons:
1 - It is decoupled to channels, but can be easily integrated with it passing content through a channel.
2 - It is integrated with the receive operation which is consistent with receive(promise) feature already implemented in Aryn.
3 - Allows natural error handling with try/catch, example:

aryn.run(function*(){
    try {
        var content = yield receive(fs.readFile('file.txt'))
    } catch (e){
        console.log(e)
    }
})

So, what do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.