How do Fibers and Meteor.asyncWrap work?

Check this out:

a = function(b, callback) {
  console.log(b, callback)
}
f = Meteor.wrapAsync(a)
console.log(f(b))
// > [Function] undefined

The problem looks like wrapAsync looks for the first function, not the last function…

Going straight to the code can help a lot . . . sometimes :

// Meteor application code is always supposed to be run inside a
// fiber. bindEnvironment ensures that the function it wraps is run from
// inside a fiber and ensures it sees the values of Meteor environment
// variables that are set at the time bindEnvironment is called.
//
// If an environment-bound function is called from outside a fiber (eg, from
// an asynchronous callback from a non-Meteor library such as MongoDB), it'll
// kick off a new fiber to execute the function, and returns undefined as soon
// as that fiber returns or yields (and func's return value is ignored).
//
// If it's called inside a fiber, it works normally (the
// return value of the function will be passed through, and no new
// fiber will be created.)
//
// `onException` should be a function or a string.  When it is a
// function, it is called as a callback when the bound function raises
// an exception.  If it is a string, it should be a description of the
// callback, and when an exception is raised a debug message will be
// printed with the description.
Meteor.bindEnvironment = function (func, onException, _this) {
  Meteor._nodeCodeMustBeInFiber();

  var boundValues = _.clone(Fiber.current._meteor_dynamics || []);

  if (!onException || typeof(onException) === 'string') {
    var description = onException || "callback of async function";
    onException = function (error) {
      Meteor._debug(
        "Exception in " + description + ":",
        error && error.stack || error
      );
    };
  }

  return function (/* arguments */) {
    var args = _.toArray(arguments);

    var runWithEnvironment = function () {
      var savedValues = Fiber.current._meteor_dynamics;
      try {
        // Need to clone boundValues in case two fibers invoke this
        // function at the same time
        Fiber.current._meteor_dynamics = _.clone(boundValues);
        var ret = func.apply(_this, args);
      } catch (e) {
        // note: callback-hook currently relies on the fact that if onException
        // throws and you were originally calling the wrapped callback from
        // within a Fiber, the wrapped call throws.
        onException(e);
      } finally {
        Fiber.current._meteor_dynamics = savedValues;
      }
      return ret;
    };

    if (Fiber.current)
      return runWithEnvironment();
    Fiber(runWithEnvironment).run();
  };
};

Hmm. When it says environment variables, does that mean process.env? Or does it mean variables that are scoped only to the current user session?

Oh, it’s got nothing to do with OS variables.

Keeping control of “this”, and the scope of variables available at any given point during execution, is really hard in Javascript – maybe harder than any other language. NodeJS and aysnc calls make it worse. Meteor’s sync layer on top of that guarantees a trip down the rabbit hole at some point.

If you want to do more than just develop user CRUD with Meteor, deep deliberate experimentation first with bind(), call() and apply() followed by Fibers and Futures on plain NodeJS, followed finally by Meteor.bindEnvironment() and Meteor.wrapAsync() will help you foresee the rabbit hole before you fall down it.

2 Likes

I’ve stayed away from Meteor.wrapAsync because it requires that your callback function have a signature of function (error, something). So if you tried to use it with, for example, the famous request NPM package, which takes a callback of three arguments, you would not be able to get everything you wanted.

Therefore, I’ve used Meteor.bindEnvironment to get things done, that is, to execute Fiber-dependent stuff like Mongo.Collection, etc. Also, I’ve used Meteor.bindEnvironment and Npm.require('fibers/future') to make any asynchronous function run synchronously.

For example, for this simple usage of request, if you wanted to make that operation synchronous while being able access every argument that comes out of the callback, you can do:

var future = new (Npm.require('fibers/future'))();
var request = Npm.require('request');

request('http://www.google.com', Meteor.bindEnvironment(function (error, response, body) {
  if (! error && response.statusCode === 200) {
    future.return({
      response: response,
      body: body
    });
  } else {
    // This actually throws and you can catch it 
    // from a try/catch outside of this callback
    future.throw(error);
  }
}));

// This is a blocking call, which will give you the data
// that comes from the `future.return` call
var requestResults = future.wait();

You can use this technique with almost any NPM library.

@arunoda has done something similar in his meteorhacks:async package with the Async.runSync function, which is imply'd in his meteorhacks:npm package. The only difference is that he doesn’t use the future.throw function when an error occurs and also has limited control over the return value.

5 Likes

Now we’re talking. That looks like it might fix my issue. :slight_smile:

1 Like

Hey, so I build this simple example app to help me understand Tracker and I want to create another similar app to help me understand how Fibers work but I don’t have a good fundamental understanding of what they’re doing. Any suggestions on how to do this?

Also, why aren’t fibers using on the client?

Fibers are a NodeJS native extension, which means it’s written in C++ and added to NodeJS and thus is not available in the browser.
Google for NodeJS and Fibers and you’ll find more info, e.g. by checking the project on Github or npm.

Basically Fibers and Futures implement an alternative way of handling async code (“callback hell” – google for it), with other options being Promises, coroutines/ES6 generators, ES7 async/await. HTH!

Oh, I didn’t realize its written in C++! That looks like its above my pay-grade :wink:

Thanks for the help!

So promises aren’t built with C++ native extensions though… Are there specific advantages of fibers?

Here is how to use Meteor.wrapAsync :

function a(cb) {
    console.log('a');
    cb();
}

//We can not pass (cb, cb) as params, because wrapAsync expect 
//standard Node.js function arguments : (params, cb).
//So we need options object which can contain cb as property.
function b(options, cb) {
    console.log('b');
    cb(null, options.cb);
}

//

var bSync = Meteor.wrapAsync(b);

//We pass 'a' as property of options.
var refToA = bSync({cb: a});

var aSync = Meteor.wrapAsync(refToA);
aSync();

//Output:
//b
//a

Fibers are in a sense more powerful because they actually introduce a blocking Future.wait call and other methods that go along with that. You can’t write a blocking method call in regular JavaScript. Only because something like Fibers is “messing with” (fundamentally altering) the semantics of the program flow something like blocking on a method call is possible.

There is nothing that would theoretically keep Promises from implementing such a thing as well, but that’s not how Promises have been envisioned to work. They are possible to implement completely without altering the JS runtime itself (like v8/NodeJS on the server) and thus they are available for both the browser and the server.
And the idea of Promises has gained so much traction that browser vendors have at some point begun implemenenting native support for it, which, over time, will evolve to offer advantages over the non-native approach. Usually performance is one of those, but for now at least the “bluebird” Promise implementation is the fastest one around, but that I expect to change soon, maybe over the next year or so.

The advantages of more powerful, language-meaning-modifying extensions such as Fibers is really in making it possible to write code without callbacks. Callbacks were never a good idea from the viewpoint of “language ergonomics”, i.e. what’s easiest and most straightforward for us humans to reason about, and it will indeed take a more powerful approach than just something like Promises in order to correct the “user experience of writing and dealing with async code”, if you want to call it that.

Specifically async/await from ES7 is likely going to be a great step forward, but I expect that it will take at least 2-3 more iterations of various solutions until we will arrive, for JavaScript, at a place where we will collectively feel that the async programming model has fully matured to the point of seamlessly and completely integrating into the language, its libraries and the developer’s thinking model and daily work.

1 Like

Thanks for the explanation. This really helped :slight_smile:

I hear people sometime talk about “generators” and functions that that “yield” or “function*”. Is this at all related?

1 Like

Adding to @lai’s example, we can also just do

var requestSync = Meteor.wrapAsync(function(url, callback) {
  request(url, function (error, response, body) {
    callback(error, {response: response, body: body})
  })
})

var result = requestSync("http://google.com")

console.log(result.response, result.body)
2 Likes

I was just made aware of that haha, it’s so much cleaner!

1 Like

Is there a way to get the return value from an environment-bound function that is called outside a fiber?

In the Meteor.bindEnvironment docs below it says the return value is intentionally ignored. (what is the reasoning?)

I came across an issue with this when attempting to implement the share-access npm package with a sharejs meteor package. share-access decides whether an action is to be permitted based on the return value of the callback.

I define the callbacks inside Meteor.startup and wrap them with Meteor.bindEnvironment. I’m then able to access the Meteor environment variables but since the bound function’s return value is ignored the callback is of no use.

  shareAccess.allowUpdate( 'collection', 
    Meteor.bindEnvironment( function(docId, oldDoc, newDoc, path, session){
      // decision logic... unfortunately return value ignored
      return true;
    },
    function(err){
      console.log('bind error');
    })
  );

Is there another way to go about accomplishing this scenario?

You would need to use the full blown approach that uses Futures. See my example above.

But actually @trusktr’s example should still work?

We should actually just use async/await in Meteor 1.3+ now. On the server, async functions are implemented with Fibers. So, I’d actually just do this now instead of dealing with Fibers directly:

function asyncRequest(url) {
    return new Promise((resolve, reject) => {
        request(url, (error, response, body) => {
            if (error) reject(error)
            resolve({response, body})
        })
    })
}

async function main() {
    let result = await asyncRequest("http://google.com")
    console.log(result.response, result.body)
}

main()

See this thread on how to promisify non-promise-based Meteor APIs.

There’s also talk of making promise-versions of Meteor APIs too, which will make it easy to use Meteor APIs with async/await out of the box, without having to do the promisifying steps yourself (no need to make a function that wraps the API with a Promise and returns that Promise). For example, just

const doc = await Docs.findOneAsync(docId)
3 Likes