Thunks are functions that already have everything they need to return a value. They do not require any parameters. Kyle explains how thunks typically manifest themselves as a function wrapper around some state or token value. He also demonstrates the difference between synchronous and asynchronous thunks.
But I can describe to you what a thunk is, and it's actually kind of deceptively simple, what a thunk is. From a synchronous perspective, a thunk is a function that has everything already that it needs to do to give you some value back. You do not need to pass any arguments in, you simply call it, and it will give you a value back. Does that sound familiar? Remember how we were talking about that before in a different workshop? So a thunk is just a function with some closured state keeping track of some value or values, and it gives you those things back whenever you call the function.
Here's a synchronous thunk. I've got an add function which expects an x and a y, and I make a thunk that calls the add function and already has the 10 and the 15 in it, it's already hardcoded, in this case. So every time I call like I do on line, so that's line six to call the add function, but every I call line nine, if I call thunk over and over again, I'm going to get the same value out every single time. So now, here's the important part, thunk itself, that value which is a function, it has become a container around that particular collection of state.
And that is now a container that I can pass around anywhere in my program as a container wrapped around that state, and anytime I want to extract it, I simply have to call that function and I get the value out. I don't have to pass the value or the state itself. I pass the wrapper around that state all around my program, so it becomes like a token that represents this value, and it's easy to get it out, all I have to do is execute it to get the value out. You may not realize it yet, but that is the fundamental conceptual underpinning for what a promise is.
A wrapper around the value. Here the wrapper is a function. A promise is a much more sophisticated thing. It's actually quite powerful to think about when we extend thunks from the synchronous nature into an asynchronous nature. This is a synchronous thunk and that's the most common definition for thunks you'll find, is that it's a synchronous thing. What is an asynchronous thunk? Well, there's no right or wrong definition here but the most natural way that we can define an asynchronous thunk is that it is a function that doesn't need any arguments passed to it to do its job, except you need to pass it a callback so that you can get the value out.
So that's what we're going to call an asynchronous thunk. So let's extend our synchronous thunk to an asynchronous thunk. I have an addAsync function. It fakes some asynchronous-isity with a set timeout. It takes the callback that you've passed in on line one, on line three it calls it with the addition. So we make our thunk on line seven with addAsync and we hardcode 10,15 in cb, and on line 11 we have that value, that thunk value, that we can pass around our function and every time we call thunk and pass in a callback we know we're going to get our value out.
But here's what's incredibly powerful about this pattern. From the outside world, we do not know, nor do we have to care, whether that value is available immediately or whether it's going to take a while to get us that value. What difference does it make to us? We pass in a callback, we know you'll call the callback when you have a value ready for us. So the first time we call thunk and we pass in a callback, you might have to do some significant work to calculate the answer to our question. It might make some AJAX call and do some other kind of crazy calculations, and it might take a while.
And then it may decide to memorize the answer to that question so that the next time you call it it just gives you the value back right away. But from the outside world does it matter to us whatsoever how that works? What's happening here is that by wrapping this function around the state and allowing it to be asynchronous in nature, we have essentially normalized time out of the equation, we have factored time out of the equation.
It changed everything for me. And in fact, it's what helped me understand promises. I didn't fully understand the conceptual basis for promises until I understood what thunks were doing, because we'll find out that a promise is a time-independent wrapper around a value. Just as a fancier API. So as I said earlier, I like to say that thunks are promises without the fancy API. Okay. Now, there are some twists, there are some wrinkles that we can throw into the situation.
If I wanted to make a thunk, like give myself a generalized utility for making thunks, I could do that old crappy array stuff and pushing the arguments on and calling apply, I could do it that way. And I could use it this way as we see on line seven, so now I'm not manually making one, but I can make any thunk I want by presetting the callback as the first argument and anything else that I pass, it's going to get passed along to it always, and that's how I could produce my thunk.
So I could make myself a utility for making these, which would be not fundamentally terribly different from a promise constructor. MakeThunk would be kind of like a promise constructor, it's a constructor for thunks. Yes? - (Student) Could you go through the, so when we talked about the problems of callbacks, this Inversion of Control, not too early, not too late, not too many times? - Yep. - (Student) Can you just expound a little bit on that with how the promise thunk is-- - We're not going to, we're actually not going to have any solutions to Inversion of Control with thunks.
- (Student) Yeah, okay. - Promises are going to solve Inversion of Control, but we're not quite there. - (Student) Yeah, okay, that makes me more-- - Yeah, we haven't solved Inversion of Control yet, we're laying the conceptual underpinning for promises, and that's why we're looking at that. - (Student) Yeah, I just wanted to, 'cause that realization happened earlier, where I was like, ah, now getting at better understanding what promises are. - Yep. - (Student) Um, and just wasn't sure how much the fancy API is, you know, solving our problems or how much is the thunk solving everything.
- Yeah, got it. The thunks really aren't addressing Inversion of Control. The nature of how promises are designed are designed to solve that, so we'll get there, okay? There's a question about going over Slide 39 again. This is a synchronous thunk. As you can see, I have an add function that receives an x and a y. And I make another function called thunk, which doesn't need any arguments at all. Thunk can simply be called because it is hardcoded to know to call the add function on line six, and it's hardcoded to know to pass in the values 10 and 15, also on line six.
So everywhere we do something like we do on line nine, we're going to get the exact same value out. That's a synchronous thunk. Slide 40, the asynchronous version. Exactly the same concept, except we need to pass in a callback to our thunk. So line 11, I can't just call thunk and get the value as a return value, I have to call thunk and give it a callback which will receive that value, as I do on line one.
You're going to get a chance to practice with thunks, I promise. All right, so here's a re-writing of that silly running example calculating the meaning of life by using thunks. Now, you notice something slightly different from what I do before. On lines one and two, I pre-create some thunks. I say that get 10 needs to, when it's invoked, call getData with the value 10. So line one has not actually made that AJAX request yet. That's important to understand.
All we've done is create a wrapper that when called will do that AJAX request. Okay, so I pre-set up the get10 and the get30 thunks, and I can start using them. So on line four, I use it, call get10, give it a callback. I know a thousand milliseconds from now line one will have the value 10. Then I use the get30 thunk on line six. And you notice on line nine, I create another thunk. Why couldn't I create that thunk, the one from line nine, why couldn't I pre-create that up on line three? Because it's temporally dependent upon the responses from the first two thunks.
Does everybody see that? So that's why I had to defer the creation of that particular thunk. But on line 13, I can simply call getAnswer, which is my thunk, and I will retrieve that. Now, getAnswer happens to be a container around that value, and I can pass getAnswer all over my program and anybody that needs the meaning of life can simply call that, give it a callback, and get the answer back. Now, there's something really important to note here, as I said, it's important to note that this particular expression of a thunk is what I guess I would call a lazy thunk.
If I was really trying to come up with some terminology for it. It's a lazy thunk in that it doesn't do the work until you call it the first time. You could make an active thunk which did the work and just held on to its response, so it did the work right away and held on to the response. That is another way of doing a thunk. And I'm giving you a gigantic hint for your upcoming exercise. - (Student) So like deferring a promise or something. - It's a lot like a promise.
That's why I'm saying these are the conceptual underpinnings. So our current expression of thunks is very lazy. It is possible to do thunks in a different way, which they are active. That means the first time they are called, they do, the first time they're set up they do the work, and they don't give you the answer until you pass in the callback.
Note: This course was created by Frontend Masters. It was originally released on 3/29/2016. We're pleased to host this training in our library.
- Parallel and asynchronous code
- Working with callbacks
- Using thunks
- Exploring promise flow control
- Abstractions, sequences, and gates
- Observables, events, and sequences
- Blocking channels