Kyle Simpson: Now, just as a caveat to Exercise 5, there's some deceptive things to this exercise because it seems at first blush that what I'm asking you to do is pretty straight forward. But it's as they say, it's the devil is in the details. When you start to think about the implications of this code, you start to realize it's a little more complicated than you might be aware.
Asking you to do this exercise from scratch if you've never done anything with Promises is a tall order. So I provided the solution already using the callback approach. So this is a complete program that uses the callback pattern. If you were to run this code, if we were to open up Exercise 5, ex5.html, and we were to run it, you'll note here in my console.
Well, it's locked up on me for a second, so just a moment.
Kyle Simpson: Okay, so watch particularly not the requesting comments, because I have those in there just for debugging purposes, but watch particularly how I'm printing out first text, middle text, last text, and then complete, making sure that it's in that order.
But watch the timings on these things, because I have these set up to fake like they are randomly taking longer. Different files taking different longer. So, technically the files quote, unquote load at different times. But the mechanism that I've put in place makes sure that they always get printed out in the correct order.
So, here, we may see, if I get lucky, we may see examples where in this particular case, it looks like it's waiting for all of them to finish, because maybe the first one was the long pole in terms of delay. But if we get lucky, we might see the text peppering in one at a time, depending on my random number generator.
So there you saw just a little bit of a delay, where the first and the second ones were printed. It was still waiting on them for the last one. The spirit of this exercise is you do want to make sure they go in the correct order. But you don't want to have to wait for all of them to finish before you start rendering.
If the first one comes back quickly render it right away. Kind of in the same mind set as we'd take progressive rendering, show things as quickly as possible. So if I get lucky we'll get a random choice here where it'll actually print out the first one very early with kind of a very short delay.
And then you'll see, so there's one, it's still waiting on the second one. And then it waited just another millisecond for the last one. So the mechanism that we put into place to make sure that they stay in the proper order requires us to track some state in between three independent sets of actions.
because we need to request them all at once, but they need to modify some shared state, so that we know when all the responses have some back and we know when the first one has come back that we can render it, but if the second one comes back first we need to be still waiting on the first.
So, the point of this is that asynchronisity oftentimes requires you to track additional state between your separate actions. In this case, the loading of the files. Now, I'm just faking it. This is a fake AJAX that just sets up a random delay between one and nine seconds long.
I immediately print out that we're requesting it, but I just set up a time out and then send you the information back. So we're faking an AJAX and we're faking the contents of these files, but you could easily extrapolate that these could be real Ajax calls that on the network might have different timings just depending on.
Network conditions and so forth. So here's how I call that, I call that fake AJAX I give it a file name that I want to request and I set myself up with a callback. So I want to be notified when it happens. And I've decided to have one callback that gets called for every single one, that's called file received.
So this one gets called when all three of them are called. And we're going to internally track a state in a variable that we call responses for the responses that have come back. And so each time we get a new response that we didn't have before, we add it to our internal state.
And then we loop through our responses to figure out if there's one that we can render yet. So we start at the beginning and we say, do we have response 1? If so, render it. And then when we get to 2, we can't render 2 unless 1 was already rendered, so we just do a simple for loop through our state tracker to see whether or not it's okay to output.
And then finally, if we haven't had any cases where the responses were missing, then we know that it's time to go ahead and print out the complete message. Now there's a thousand different ways that you could track the internal state. This is not the only way. But I just wanted to show you using the callback mechanism, we are forced to track this internal state or track this state between our different tasks independent because if we want to request these things at the same time.
We don't want to wait for file1 to finish before we request file2 and so forth. And even if you're not dealing with file loading this is still a very common thing. You may have database requests where you want to fire off three database requests to three different tables. You don't know what order they're going to come in but you need to make sure that you order the responses in your JSON object properly so you need to make sure that they go into the correct slots.
So this is the kind of problem that seems to crop up an awful lot in asynchronous flow control. And before Promises many of us just hand rolled our own solutions similar to things like this. And the purpose of this exercise, if you've never done anything with Promises is to sort of explore.
So you may be confident enough to try to write your own code if you've looked at some Promise code before. Or you may feel like, I just want to study the existing code. So that's what we're going to do here. I'm just going to show you, using first my library and then I'm going to show you the same way using native Promises.
I want to show you one way that we could do it using asynquence. So the same utilities is true before, we have a fake AJAX that's going to receive a callback, we have our output function, all that stuff stays the same. The stuff that changes is the stuff that's underneath these little stars here.
All right, so I set myself up with a different kind of utility, and the previous one I called it getFile, and he used callback methodology. But here my getFile is going to do something different. I'm going to set up an asynquence. I'm going to set up a sequence, which is essentially like setting up a promise.
And when that promise, and when that fake AJAX call calls my callback, that will notify my promise that it has been completed. But I'm returning my promise back so that I'll be able to chain off of that. I'll be able to listen for that continuation event if you remember from yesterday.
Now I want to make sure I call all the files in parallel. So technically it may look like I'm calling them one at a time, but because each one of these is an actual function call, I will request all three functions right away, I mean all three files right away.
Now, the getFile function, every time we call it, it returns back a different promise, a different sequence. So I'm going to have three different promises that I'm going to slot into a sequence of steps. And you'll note here that the way the sequence is written, it makes it very clear the behavior that we want.
It's a very declarative style of express our asynchronous flow control because I'm saying when file1 finishes loading, then I want you go ahead and output file1. And when file2 finishes loading, output file2. Now if file2 finishes early, if it finishes before file1, that promise just sits there and waits for us.
Because Promises will track that internal state that they have been completed early before we've listened to them. That's one of the functionalities that Promises give us. So even if file2 finishes, even if file3 finishes before file1, because we've expressed this in a chain, a series of things that we want the steps to happen sequentially, then we know that file2 will be loading, and we'll make sure to wait.
If file2's not there by the time we get to it, then we just pause the chain. But if it is, we go on. And the same for file3. So expressing code like this allows a developer, I think, to reason about their asynchronous flow control in a much more reasonable way than in the previous code snippet.
They functionally do the same thing. They're functionally tracking the same sorts of state, but in a very different way, and they produce a different source program. And I think I said this yesterday, but if I didn't, it bears repeating. Source code is not written for computers. Source code is written for developers.
The computer only cares about ones and zeroes. And there's literally an almost infinite number of source code programs that could produce the same exact series of ones and zeroes. So, you're not choosing to write source code because you want to help the computer. You're choosing to write source code in a particular way because you want to help yourself and your other team members.
So, when we write this asynchronous flow control, the goal is to write it in such a way that it makes it easier for us to maintain mentally, to understand, to look at this code. And I think asyncquence does a pretty good job of that. You might not, it's okay.
I'll show you the native Promise approach to it, and you see whether or not you feel like that's a better way. So, in this particular way, we've created ourselves a chain. It's very clear what our steps are going to look like. And if there's ever some problem, we know exactly where in that chain to go looking.
Now if I-
Kyle Simpson: Yeah.
Speaker 2: So when we say we're having to get all these files and parallels right?
Kyle Simpson: The files will be requested in parallels.
Speaker 2: But the dots sequential to the promises are chained in sequence.
Kyle Simpson: Yes, correct. That's exactly what it's doing. It's making sure that things will go in a sequential order, a series.
Speaker 2: But the files are requested in parallel.
Kyle Simpson: Mm-hm. And so if file2 comes back well before file1, there's something that needs to track the internal state of that. And we've chosen to let the promise do that rather than what we were doing in the previous file where we had to have some data structure to track that stuff.
Okay? Now, if we wanted to just use the plain old native Promises, no Promise abstractions, no anything, what would that look like? Again, the stuff at the top is completely the same. The only stuff that's going to change, we have our getFile, which should look almost identical to the previous one.
We're going to construct a promise object rather than to construct a sequence, so that part starts to look pretty much the same. We call the resolve function on it. But here's where things start to get a little different, because we don't have quite as much sugar built into the native Promises.
Things aren't going to be quite as pretty. They'll still functionally work but they're not quite as pretty. The first thing you'll note is that really the only way practically to do this, something in parallel is to call all these methods literally in parallel and we need to track those promises individually because there's no API like I've built into asynquence to automatically bring those three promises into a sequence like that, there's no facility for that directly.
So I have a promise one, a promise two, and a promise three. That ensures that I've made my requests in parallel. And here's how I chain them together. I take advantage that the first one is a promise, so I can call the then method directly on the first promise and do an output.
And then after that output's done, It would have been nice if kind of like we did with asynquence if there was a sugar that would allow me to pass that promise directly into the chain. But unfortunately the native Promises don't allow that so we have to do a little function that returns our promise.
So that will chain the promise two into this part of the chain and then an output. And then chain promise three into this part of chain and then an output. Still, functionally it's pretty similar in terms of being able to reason about it. A little bit less sugar.
So the comparison there is to give you that abstraction libraries provides you better sugar, which gets more and more of the cruft out of the way and lets you focus more of your attention on the things that you actually care about rather than the cruft. Okay. But, either way, using the built-in one or using the library, it's pretty reasonable to express this in a Promises mindset.
Just real briefly I want to show you, this is going to look a lot more complex, but it's actually going to betray that there's a whole lot more power under the covers than what we're doing. Because what if this wasn't a preset number of promises that we knew about?
What if we were loading potentially hundreds of files or potentially hundreds of database requests? We didn't really know how many there were. You couldn't practically write out p1 through p1000 and manage that code, so how would we do this in a way that could use utilities, that have looping capabilities, and so forth?
We can use the array functionalities of map and reduce to solve those problems pretty nicely. So again, I will first show you using the asynquence approach. Using the asynquence approach, what I'm going to do is I'm going to have an array of these things. In this case, it's still just three, but it could be an array of a thousand of them.
I'm going to use the map function, which map, if you don't know what it does, it takes an array, it runs the function against each element in the array, and whatever that function returns replace the original element. So it's doing a one-to-one mapping of elements in array from one thing to a transformed version.
In this case, sometimes people do that to like transform a bunch of strings to uppercase or something like that. In this case, I'm going to transform the first array of strings, which are file names, into an array of promises. Basically an array of sequences. So, that's why I'm calling .map(getFile).
Remember, getFile takes an input and it returns an output. So that's exactly what we want. We want a function that takes something in and returns a replacement. So we can just call map(getFile). And that will now leave us, if we were to inspect that in a console, we would now, instead of having an array of strings, we'd have a string of three asynquence promises.
Okay? Now, I'm going to call map again on that array, so I'm going to do another level of doing it. So I'm going to be taking in sequence for each one of the elements in the array, and what I'm going to do is tack on, chain on, a output to each one of those promises.
So I've created essentially through a looping mechanism, the exact same thing that we did manually with the one, two and three in our previous code snippet. And I take that full array, that complete array that's been produced from that which is now an array of promises that are loading in parallel but they've got their outputs chained in the proper order.
And I pass all of that array into my asynquence API using the normal apply approach that you would have available to you. And, finally, the output works. So this code works. And this is more flexible, it's more powerful to us now because we don't have to manually code our sequence, we can take advantage of looping.
Finally, how would we do this same sort of technique, how would we do it with Promises? It looks similar, but it's going to be a little bit different because, again, the native Promises don't have quite as much sugar to them. Again everything at the top is the same. Our getFile is the same.
The only thing that's going to change down here, the first thing that we're going to do, we're going to do an array of our elements and we're going to do a map(getFile) just like we did before. But now, because we're not using asynquence and we don't have some of that sugar, we're going to need to do a little bit more work to massage what we're doing so we're going to call a reduce function rather than another map function.
And I won't get into all the details of reduce, but reduce starts with an initial state of an element, and it passes that in for each element and the idea is that you sort of add on or chain on. So in this case we're going to start with a promise, and we're going to for each element in our array we're going to chain on our promise to that and pass the chain along.
So at the end we get one big chain that has all of our promises chained together, and we used reduce to do that. And then of course finally we call then to output our complete. So hopefully that provides you some good code to go back and look at and compare yourself as you're starting to try to think about Promises.
You can kind of get a sense of doing things kind of the manual way which produces nice pretty looking code but you also have the power to use these looping constructs in a pretty powerful way as well. Any questions about that exercise before we move on?
Speaker 3: Is promise similar to threads.
So, we don't have any worries about thread safety. We do have shared memory. So we do have that issue in concurrency, and Promises kind of not really completely reduced the idea of shared memory, but they go more towards a functional nature where there isn't a shared memory block that you're managing.
You're managing state inside of each promise. So the fact that you have a bunch of promises chained together is what manages the state, rather than having some big global variable that everybody modifies concurrently.
Speaker 3: Okay, no issues of.
Speaker 4: The question from Derek A, is your coding style a specific pattern with regards to dropping the dots and chaining methods like this or is that just a personal habit?
Kyle Simpson: It's to make things easier for you to read. It's not really even a particular style that you should do.
I tried to make it easier to read when I'm illustrating things for people. So, I'm trying to make it clear that this is a separate chain from that.
Speaker 4: And it's allowed in the langauge because it's just waiting around to see, the statement doesn't end until the semicolon, right?
Kyle Simpson: Correct.
Speaker 4: Is that why it's allowed?
Kyle Simpson: Yep. It's pretty common,. That's an idiomatic style that comes kind of from JQuery world. It's pretty common that you put each chain element on it's own line or whatever.
Speaker 4: We have another question.
Kyle Simpson: Yeah.
Kyle Simpson: It's a full book, not just a chapter. But yes, the async and performance book is going to heavily deal with Promises, generators, co-routines, all that stuff.
Speaker 4: And your async library?
Kyle Simpson: And asynquence, yes. The typical style that I've tried to do is in these books, the main chapters of the book deal with the core stuff that we know about the language or that we know about.
And then I would probably use an appendix to discuss my own library. Okay. I think that's it for what we're going to go through on those old exercises. Again, if you run across any other questions at any time, it's kind of an open invitation to everybody that takes a workshop with me, as you go through this a month from now or six months from now, if you have questions, feel free to reach out.
There's a lot of exploration and things like that and I'm not saying I have it perfect or right. It's just to get you going thinking about this sort of stuff, yeah?
Speaker 5: Where is the context by the time you get to that last end?
Kyle Simpson: In which file?
Speaker 5: In this last one, like.
Kyle Simpson: The Promises one?
Speaker 5: Yeah, what's your this at the end?
Kyle Simpson: We're not dealing with any this style coding at all. I actually don't really like this style coding. Remember I said 95% of the time I use kind of the lexical model for coding.
So Promises don't really stress anything about a this binding. And I completely avoid that topic entirely in asynquence, there's no this binding at all.