1

I can't find any good documentation (or any question on SO) that explains how exactly yield and run works.

I am not able to find how would an asynchronous method will be able to return a value using Fibers/futures.

For example (code not syntactically correct), how can I make this function return the response synchronously

  function findData( param )
  {
    var fiber = Fiber( function(){
      var currentFiber = Fiber.current;
      Model.findOne({ "param" : param}, function (err, data) {
        response = { err : err, data : data };
      });
    });
    return fiber;
  }

Something like

  var value = findData("1");

This Model is an object that I get from Mongoose schema class (not sure if it is relevant).

Thanks in advance.

gurvinder372
  • 66,980
  • 10
  • 72
  • 94
  • From what I can see, `fibers` is similar to [`co`](https://github.com/tj/co) or Bluebird's [`coroutine`](http://bluebirdjs.com/docs/api/promise.coroutine.html), only less standard. I would suggest taking a look at those modules instead (or go ES7 and use [`async/await`](https://jakearchibald.com/2014/es7-async-functions/)). In any case, you can't make async code synchronous with any of these, it just _looks_ more sync. – robertklep Sep 26 '16 at 13:32
  • @robertklep thanks, will take a look – gurvinder372 Sep 26 '16 at 13:41

2 Answers2

1

Fibers are not new invention

Node fibers make it possible to suspend the running of any function by saving the state of the current executing environment in a platform dependent way at the lowest level (For example windows has a fiber concept, not widely used, more lightweight than a thread, not preemptive).

Other libraries simulate co-routines using language features

All other js libraries implement co-routine continuation by using callback functions, storing the execution state in scope variables. This means you either have callback pyramid, a promise chain, or async/await (I put decorated generators in the same bucket as async/await).

Fibers are also a possible implementation of co-routines. Fibers should be fast, and integrating them in your code does not require you to write in a different codestyle, or introducing new syntax. Execution contexts (stack, registers, etc...) which can be changed to and from at will, from your own code.

This cannot be done in pure JavaScript, node-fibers use native libraries to achieve this!

Node fibers restrict you so you don't block the event loop

The node-fibers specific concept is: the javascript event loop is outside of all fibers, thus your initial code runs without fibers too. If you have a fiber reference, you can pass the right to run to it by fiber.run();. When you are inside a fiber, you can give up the right to run by calling Fiber.yield(); (effectively suspending the currently running code), and the javascript event loop will continue. All builtin callbacks (setTimeout, Promise.then, event handlers, http request callbacks) will run in the javascript event loop, without a fiber.

See this example

const Fiber = require("fibers");

function findDataAsync(param, callback) {
  setTimeout(() => {
    callback(null, "Async returned data");
  }, 100);
}

function findData( param ) {
  const currentFiber = Fiber.current;
  var response = null;

  findDataAsync(param, function (err, data) {
    response = { err : err, data : data };
    currentFiber.run();
  });
  Fiber.yield();
  if (response.err) {
    throw response.err;
  } else {
    return response.data;
  }
}


function main() {
  console.log("Inside fiber started");
  console.log(findData());
  console.log("Inside fiber finished");
}

console.log("Outside fiber started");
Fiber(main).run();
console.log("Outside fiber finished");

This should output:

Outside fiber started
Inside fiber started
Outside fiber finished
Async returned data
Inside fiber finished

Notice that Outside fiber finished is logged immediately after the first yield in the fiber is called.

As you see, we had to start a fiber immediately to be able to yield. If you try to use fibers in a third party library, you have to make sure that the library does not "reset" your current execution context to the javascript event loop by calling setTimeout or issuing asynchronous http requests.

Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
1

Change your function to:

function findData(param) {
  var currentFiber = Fiber.current;
  Model.findOne({ "param" : param }, function(err, data) {
    if (err) fiber.throwInto(err);
    else fiber.run(data);
  });
  return Fiber.yield();
}

Then you can write:

function doSomething() {
  var param = ...;
  var data = findData(param);
  processData(data);
}

function doLotsOfThings() {
  ...;
  doSomething();
  doSomethingElse();
}

And so on, and so on... You can write all your code as if Model.findOne was sync.

The only gotcha is that you cannot call any of these functions directly from node's event loop. You have to call them inside a fiber. Typically, you will create the fiber in your HTTP listener (or TCP listener, or other). Typical code:

http.createServer(function(request, response) {
  // you cannot call doLotsOfThings() here
  Fiber(function() {
    // but you can call it here
    try { doLotsOfThings(); }
    // and this the right place to catch exceptions too!
    catch (ex) { handleException(ex); }
  }).run();
}).listen(8124);

In short, you will call Fiber.yield at the low level, when calling async functions (first pattern above) and you will create the fiber in your top level listeners (second pattern above). All the code in between can be written in sync style.

Note: With these code patterns you don't need to trap/test errors in every function. Instead, you can use classical structured exception handling (let the exceptions bubble up).

Bruno Jouhier
  • 1,013
  • 9
  • 11