Meteor's async execution model is based on fibers which is essentially a patch to V8 providing blocking calls making it somewhat harder for a programmer to release zalgo. Fibers work in a way similar to ES6 generators/coroutines (even though generators are syntactically more explicit); when an asynchronous call is made, the running fiber voluntarily gives up execution, suspends and lets another fiber have a go at the CPU to be resumed at a later point in time. Kind of like a turn based board game where each fiber is a player.
Now, this allows for easy orchestration of calls that happen one after each other - they each block so you can just call them in sequence. But at the same time it makes it a bit harder to run things in parallel. Because - how?
Well, using fibers of course. Or to be precise, futures
which is a convenient abstraction built on top of fibers (quite similar to promises). Let's try an example. Say we want to get 10 random GIFs from Giphy. Here's the corresponding serial code (the reported run times are just orientational):
function getRandomGif() {
return HTTP.get('http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC');
}
console.time('serial');
var results = _.times(10, getRandomGif);
console.timeEnd('serial');
Extremely simple, right? No callbacks at all. It's also quite slow: serial: 3982ms
. That's because it issues a new request only after the previous one has completed: it blocks on the HTTP.get
call and only resumes execution when V8 completes the network call.
To compare, this is how we can run the 10 requests in parallel:
var Future = Npm.require('fibers/future');
function getRandomGif() {
return HTTP.get('http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC');
}
console.time('parallel');
var futures = _.times(10, function() {
// Wrap the call in a future to execute in a new fiber.
return Future.task(getRandomGif);
});
// Wait for all the futures to complete.
Future.wait(futures);
// Gather the results.
var results = _.map(futures, function (f) {
return f.get()
});
console.timeEnd('parallel');
This code prints: parallel: 1388ms
, i.e., it runs cca 3 times faster than the serial version. That's because the HTTP.get
calls are executed by V8 concurrently each in its own fiber. The code is slightly more complicated because we need to wait
for all the fibers to complete and get
the results but it utilises V8 more effectively.
Futures are provided by the fibers/future
npm package. This package also exposes the raw Fiber
implementation but you (most probably) won't need it as Future
is a much more convenient higher-level abstraction.
Happy meteoring! I'm @tomas_brambora.