He had found a Nutri-Matic machine which had provided him with
a plastic cup filled with a liquid that was almost, but not quite,
entirely unlike tea.
The way it functioned was very interesting. When the Drink button
was pressed it made an instant but highly detailed examination of
the subject's taste buds, a spectroscopic analysis of the subject's
metabolism and then sent tiny experimental signals down the neural
pathways to the taste centers of the subject's brain to see what
was likely to go down well. However, no one knew quite why it did
this because it invariably delivered a cupful of liquid that was
almost, but not quite, entirely unlike tea.
from The Hitchhiker's Guide to the Galaxy
Green (co-operative) threads for JavaScript! A port of this library.
CSP - S = Communicating Processes = Green Threads!
- It makes writing concurrent software much simpler by getting data out
of callbacks through the use of magic portals called
channels. - It provides green threads via
goblocks that can park and be multiplexed over the event loop, and communicate over channels.
You can see a demo explaining the core concepts here.
// a channel
var ch = chan();
// go blocks execute their functions
// on a separate 'thread'.
go(function () {
// when a value is available on ch, takes it
// and calls the callback. returns the return value
// of the callback. can only be used inside go blocks.
return take(ch, function (v) {
console.log(v);
});
});
// the above can also be written as
gotake(ch, function (v) {
console.log(v);
});
// send a value on the channel
// also takes an optional function,
// like gotake
goput(ch, "hi!");
// => hi!
// there's also gocall
gocall(alert, 'hi!');We can spawn thousands of these 'threads', and structure our code around producers, consumers, and queues.
Let's look at an everyday async call to the database to fetch a string corresponding to the given id:
function asyncCallback () {
getUserName('id1', function (resp) {
var massaged_resp = resp.toUpperCase();
console.log("via callback:" + massaged_resp);
console.log("but can't access outside callback :(");
});
}The function fires an async query to the db and returns immediately.
In this implementation, the response is locked inside the callback and whatever code needs access to it should be put inside that callback.
This leads to what is called callback-hell,
which can be escaped with the help of channels and go blocks.
function asyncChannel () {
var ch = chan();
getUserName('id1', function (resp) {
goput(ch, resp);
});
return gotake(ch, function (resp) {
massaged_resp = resp.toUpperCase();
console.log("via channel/go:" + massaged_resp);
return massaged_resp;
});
}In this version, we have modified the callback to just put the response onto
the channel ch. We then wait for the response in a separate go block, and
return the massaged value. The go block returns a channel that will eventually
have massaged_resp. So now we can do:
var ch = asyncChannel();
gotake(ch, function (v) {
console.log("escaped callback hell!" + v);
});Here's a port of the Hot Dog Machine
function hotDogMachine(inCh, outCh, hotDogsLeft) {
if(hotDogsLeft > 0) {
gotake(inCh, function(input) {
if(input == 3) {
goput(outCh, "hot dog", function () {
hotDogMachine(inCh, outCh, hotDogsLeft-1);
});
} else {
goput(outCh, "wilted lettuce", function () {
hotDogMachine(inCh, outCh, hotDogsLeft);
});
}
});
}
}This function starts a new cooperative process that will consume from inCh
and put on outCh. If input is 3, it dispenses a hot dog and decrements its
count. If input is something else, it despenses wilted lettuce.
var inCh = chan();
var outCh = chan();
hotDogMachine(inCh, outCh, 2); // start async process
goput(inCh, "pocket lint");
gotake(outCh, console.log);
// => wilted lettuce
goput(inCh, 3);
gotake(outCh, console.log);
// => hot dog
goput(inCh, 3);
gotake(outCh, console.log);
// => hot dogFor writing async consumers like the one above, we can also use goconsume:
var c = chan()
goconsume(c, function(inp, recur) {
if (inp) {
console.log(inp);
recur();
}
});
goput(c, "hi");
// => hi
goput(c, "bye");
// => bye
goput(c, false);
// consumer dies
goput(c, "ping");
// damning silencegoconsume also takes the state to be maintained across calls as an optional argument:
var c = chan();
goconsume(c, function(inp, recur, n) {
console.log(n, inp);
if (n+1 < 3) {
recur(n+1);
}
}, 0);
goput(c, "hi");
// => 0, hi
goput(c, "hola");
// => 1, hola
goput(c, "bye");
// => 2, bye
goput(c, "ping");
// crickets chirpinggoproducealts!close!
Copyright © 2017 Divyansh Prakash
Distributed under the Eclipse Public License either version 1.0