Transcript
  • Meteor

Using Fibers to Write Synchronous Style Code

From the class:  Meteor Fibers and Dynamics

I'm going to show you in this video how we can use fibers to write synchronous style code, even though we're using asynchronous functions from Node.js or other libraries like the Mongo driver. So before we do that, I want to show you this function that I wrote that simulates doing some asynchronous work. And it simulates doing the work by using a set timeout call. So let me show you why that works.

Right up here, what we're doing is when we call do async I/O, we pass in this callback. And at two seconds later, it's going to put the callback back onto the task queue. So we'll get the first callback here.

And then if we were to call do async I/O again, the callback would be put on the task queue again two seconds later-- callback 2, and so on. So these functions would be executed at a later time. So even though they're asynchronous-- so they get executed at a later time-- we're not actually going to do any concurrent work. So we're not going to do any I/O, like making a call to Mongo. But either way, it should help us to understand how the function is executing, so in what order our functions are getting called.

So to show you an example here, let me clear the screen. And inside the code, we'll just call this function a few times. So I'll call do async I/O. And two seconds later, we'll just print out. We're done. And when Meteor refreshes, you can see on the right that about two seconds later it'll print out that we're done.

So let's build on top of this now. We're going to create a function called a handle request. What we want to do is to simulate the idea of multiple requests coming in. And we'll just take a number so that we can differentiate across the different requests. And what this will do is call do async I/O. And the callback will just print out to the screen that we're done processing requests, and we'll just say the number of the request.

Now, before we do the async I/O, I'm also going to print out that we're starting, or we're handling, the request so we can see the time difference. So let's call this function once and we'll pass in one here. And keep your eyes over on the right. And you can see that we're handling the first request and then, two seconds later, we're done with the request. So there's some asynchronous stuff going on here, but we're not actually doing any work. OK.

Let's call that again, and now we want to process the second request. Now the key here is that we want to process the second request while the first request is still doing its I/O. And so, we should be able to start the second request and when the I/O is done in the first request, it just gets executed later. So let me show you.

So you see both requests get handled almost right away and then their I/O functions complete both about two seconds later, and then that prints to the screen one by one. Let me add a few more requests so that we can simulate doing four requests instead of two. And when I refresh that, you should see that we're handling four requests, and then two seconds later we get the results.

Now what I want to do is to change this function to be written in a synchronous style. And what that means is instead of passing in a callback like this, I'd like to just get a result and block until we actually have something. And when we do, I'll just print out that we have a result. And I'll just write the document directly to the screen.

So we can use fibers to do this. Right now, if we were going to try this, we would just get some strange results because do async I/O doesn't actually return anything. And so what you'll see is a bunch of undefined printed to the console.

And, in fact, since we're not passing in a callback, this won't work anymore either. So let me save that. And now you're just noticing that the requests are being handled and then right away we're printing out to the console the number of the request and then the document. But that's undefined because this function here just returns undefined by default.

So let's use fibers to fix that. Inside of the do async I/O function, I'll get a reference to the currently executing fiber. And I can do that by calling fiber.current because there's only one executing at a given time. Then down at the bottom, what I'll do is yield by calling fiber with a capital F, the yield function. That'll pause execution of this fiber and store away the call stack, just like we saw before, until it's manually resumed by calling the run method on the current fiber. And I'll return this because we want to return the result.

Now what we need to do is to resume the fiber at some future time. And the place to do that is right here inside of the callback of set timeout. So in two seconds, we're going to resume the fiber. So I'm going to say fiber.run. And we can pass in parameters to the run function, just like we did before. And that's where I'll put the result.

So now instead of calling the callback, we're just resuming execution of the function. And any caller of do async I/O will just block because we're calling yield until we call run again, two seconds later. Now there's currently a bit of a problem here, which is that we haven't actually created any fibers. And so the current fiber is just not going to be defined.

So what we need to do is to come down here and in each one of these requests, we will wrap it in a fiber. And I'm going to create another function here so that we can call the handle request function with a parameter and see it clearly. And we're just going to run the fiber right away. We won't wait.

To save some time, I filled in the rest of the fibers. And when I press Save, let's see what happens over in the right. So all four requests are handled right away, just like they were before.

So take a look at this line. All the four of the requests are handled almost right away. That's this line here being printed.

But then right at the point of doing async I/O, the fiber for each request just yields or pauses and doesn't resume running until later. But that doesn't stop the next fiber from running. And so, each one of these fibers is able to get to its point in execution until it actually calls yield, which yields to the next fiber.

So then what happens is about two seconds later, we get a result for each one of the fibers. And that's when they've all resumed running. And down here, each one of them prints out their result. So what we're seeing here is this same exact result as we saw by using callbacks, except now we're writing our code in this nicer, synchronous style. So it's a bit easier to read and we don't have the callback hell problem that we saw before.

So one of the things to note about this is that the fiber gets created here, further down in the call stack. And by the time we get up to calling async I/O, what it's doing is participating with the fiber implementation. And so, you could call it a fiber aware function. But the fiber actually gets created down here.

So this function is designed to work with fibers. But the problem with this is we don't always have control over the libraries that we want to use. And so, sometimes we have to wrap them to work with fibers correctly. And so Meteor provides us a way to do this by using a method called a wrap async.

So let's just convert this back to a regular callback function like what we had before. And then what we'll do is create another function called do sync I/O. And we can just assign it to the result of Meteor dot underscore wrap async. And then we'll pass in the do async I/O function as a parameter.

And so, this will actually return a new function, one that wraps this one and makes it fiber aware. So instead of calling do async I/O here, we can just called do sync I/O. And hopefully when I save this, it should just continue to work like it did before.

So there we go. We're handling all four requests right off the bat. And then two seconds later, again, we're getting a result and printing it to the console. So wrap async is a nice utility to help us wrap asynchronous methods that may come from other libraries, like the Mongo native driver or other APIs you might be using and turn them into fiber aware methods.

So when we program in Meteor, we can write our queries to Mongo using this kind of style instead of passing in a lot of callbacks. And that's because Meteor handles all of this fiber creation and making the Mongo driver fiber aware for us. And so this is oftentimes just created for us automatically. But it's important to understand how all this works because it affects how your Meteor applications run, everything from performance to some errors you might be getting, like meteor code must always run in a fiber. So now you know why you're able to write your code in this style instead of using callbacks.