Skip to content

Instantly share code, notes, and snippets.

@creationix
Last active November 16, 2022 11:00
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save creationix/6416266 to your computer and use it in GitHub Desktop.
Save creationix/6416266 to your computer and use it in GitHub Desktop.

Generators vs Fibers

Both ES6 generators and node-fibers can be used to block a coroutine while waiting on some I/O without blocking the entire process. Both can do this for arbitrarily deep call stacks. The main difference between the capabilities of the two is how explicit the syntax is.

Generators - Safe, but Explicit

In code that uses ES6 generators:

var run = require('gen-run'); // https://github.com/creationix/gen-run
var http = require('http');   // http://nodejs.org/api/http.html

// A simple HTTP server that handles API requests and responds with JSON responses.
// The core logic is all in a generator function named handleRequest.
http.createServer(function (req, res) {
  run(handleRequest(req), function (err, body) {
    if (err) {
      res.statusCode = 500;
      res.end(err.stack);
      return;
    }
    res.statusCode = 200;
    res.setHeader("Content-Type", "application/json");
    res.setHeader("Content-Length", body.length);
    res.end(body);
  }); 
}).listen(8080);

var query = require('some-random-library').query;
var requestCount = 0;
function* handleRequest(req) {
  requestCount++;
  // At this point requestCount is the right value.
  var result = query(req.url);
  // At this point it's still the right value.
  // Normal function calls cannot suspend code, even inside generator bodies.
  result.requestCount = requestCount;
  return new Buffer(JSON.stringify(result));
}

Call this with as many concurrent requests as you want. Each will have the correct request count. However if the query library wanted to perform some I/O down the road, it would need to return a continuable and we would need to call it using a yield or yield*. Those three lines would then look like:

requestCount++;
var result = yield* query(req.url);
result.requestCount = requestCount;

...and we would be able to see locally that our requestCount value is in danger of changing since another http request could come in while we were waiting for the the query result.

Fibers - Powerful, but Dangerous

Here is the same example using node-fibers. Since the APIs are so different, I wrote a simple wrapper called runFiber that makes node-fibers work more like gen-run for easy comparison.

var Fiber = require('fibers'); // https://github.com/laverdet/node-fibers
var http = require('http');    // http://nodejs.org/api/http.html

// A simple HTTP server that handles API requests and responds with JSON responses.
// The core logic is all in a generator function named handleRequest.
http.createServer(function (req, res) {
  // See below for definition of runFiber
  runFiber(handleRequest, req, function (err, body) {
    if (err) {
      res.statusCode = 500;
      res.end(err.stack);
      return;
    }
    res.statusCode = 200;
    res.setHeader("Content-Type", "application/json");
    res.setHeader("Content-Length", body.length);
    res.end(body);
  }); 
}).listen(8080);

var query = require('some-random-library').query;
var requestCount = 0;
function handleRequest(req) {
  requestCount++;
  // At this point requestCount is the right value.
  var result = query(req.url);
  // At this point it might have changed, we don't know!
  // Normal functions can suspend you if you're running inside a fiber.
  // They can detect this without you telling them.
  result.requestCount = requestCount;
  return new Buffer(JSON.stringify(result));
}

With node-fiber, there is no special yield* syntax. Functions can load the "fibers" module and lookup Fiber.current to get access to the currently running fiber. The advantage of this is that if you later decide your function needs to do some I/O, you can suspend your caller's fiber and resume once the I/O is done without asking their permission or telling them you have that ability.

As promises, here is the API shim to make them act somewhat the same.

function runFiber(fiber, arg, callback) {
  Fiber(function () {
    var result;
    try {
      result = fiber(arg);
    }
    catch (err) {
      return callback(err);
    }
    callback(null, result);
  });
}
@polotek
Copy link

polotek commented Sep 3, 2013

Thanks, this is really helpful. I didn't realize that you could forcibly suspend fibers. I had a different model of how they worked that was definitely wrong.

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