Search code examples
javascriptnode.jsyieldnode-fibers

What is the difference between run vs yield in node-fibers


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.


Solution

  • 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.