Skip to content

Instantly share code, notes, and snippets.

@creationix
Created November 8, 2011 22:14
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save creationix/1349451 to your computer and use it in GitHub Desktop.
Save creationix/1349451 to your computer and use it in GitHub Desktop.
Lua style coroutines in nodeJS proposal
var FS = require('fs');
var Fiber = require('./fiber'); // Not to be confused with the existing node-fibers module
// readFile is a normal non-blocking, async function, but internally it can use
// the coroutine helper fiber. These kinds of coroutines are no more dangerous
// to the caller than any other async function.
function readfile(filename, next) {
// The call to Fiber is non-blocking. It's contents are only run sync till
// the first call to wait and then it returns.
Fiber(function (resume, wait) {
var err, fd, stat, chunks;
console.log("opening...");
// This is a normal async function. It doesn't know anything about coroutines.
// The FS.open call is non-blocking, even for this coroutine
FS.open(filename, "r", "0644", resume);
// Destructuring assignment, wait returns the arguments that resume got.
// The call to wait will suspend this coroutine and only resume when the
// `resume` callback eventually gets called.
[err, fd] = wait();
if (err) { next(err); return; }
// Same thing, but doing a fstat on the fd
console.log("fstatting...");
FS.fstat(fd, resume);
[err, stat] = wait();
if (err) { next(err); return; }
// Here is an example using a blocking loop to read a file in chunks
console.log("reading...");
var chunk, length, offset = 0;
do {
FS.read(fd, offset, 72, resume);
[err, chunk, length] = wait();
if (err) { next(err); return; }
chunks.push(chunk);
offset = offset + length;
} while (length);
console.log("pausing...");
// Works for other async functions too, not just node ones.
// Sleep this coroutine for 500ms
setTimeout(resume, 500);
wait();
console.log("closing...");
FS.close(fd, resume)
[err] = wait();
if (err) { next(err); return; }
next(null, chunks, stat);
});
}
// This is the implementation of the fiber helper used in the example.
// 'coroutine' is a made up addon or builtin to future javascript
// that implements lua style coroutines.
var Coroutine = require('coroutine');
module.exports = function Fiber(fn) {
var resume = Coroutine.wrap(fn);
resume(resume, Coroutine.yield);
};
@creationix
Copy link
Author

Silver bullets are as mythical as the werewolves they kill. What I want is something minimal, incremental, and as natural to js semantics as possible. This doesn't solve all problems, but it helps a lot with the main use case where people can't handle callbacks (doing several async I/O operations serially as part of a larger task). If other code has to be fiber or co-routine aware to interop then it's not a natural fit. My proposal fits easily into existing paradigms.

The reason I mention preserving the stack is when comparing this to solutions like Step. Step is syntactically almost as nice as this, but debugging is a nightmare and things like loops or if statements are impossible.

@willconant
Copy link

I think we agree completely on what makes this a useful idea and how it fits better into the Node ecosystem than raw fibers. I was mostly reacting to your comment about stack traces being more important than manageable looping and branching.

@willconant
Copy link

It looks like this is just as easy to implement with generators, which will almost certainly make it into future versions of v8, so this is probably going to be a viable strategy for future modules.

https://gist.github.com/1364503

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment