 So tonight, we're going to talk about concurrency. We're going to talk about async and await as a sort of modern approach to doing concurrency in JavaScript. And I'm Simon. So I'm a JavaScript and React engineer. I've been doing JavaScript for a long time and React for a number of years now. I have a team just outside of Jakarta. CodeFox is our company. And so you can also find me on Twitter with sstur underscore. So today, we're going to talk about concurrency. And it's kind of a technical topic. So I'll try to explain it as simply as I can. But you're definitely going to see some code. So we're going to go through some code and talk about some concepts. Maybe we should start with, is everybody a JavaScript programmer or does write JavaScript in here? OK, cool, just checking. And what about using kind of more modern ES6 constructs? Is everybody using ES6 at least has some familiarity with that? Yeah? OK, cool, because that's what the code slides that we're going to look at is all ES6. So let's talk about what is concurrency? So concurrency is doing multiple tasks in a period of time. So typically, one core can only do one thing at once, as well as other pieces of hardware can only do a single thing at a time. But over a stretch of time, we want to do multiple things. So a web server wants to serve multiple requests for a web page. A script wants to read multiple values from a database before it returns the request. And so what we're really talking about is doing multiple tasks over a period of time. And some of those things we can do in parallel, and some things we have to do one at a time. And so generally, order-dependent or partially-ordered units of work is where concurrency happens. So this is just an example of multiple tasks that may start at various different times, but are kind of running over the same period of time. So they're running in parallel, essentially. Some of these are. And so this is something that is pretty hard to do in computing. In particular, it's hard to reason about. It leads to race conditions. It leads to difficult models of things changing over time. And so concurrency is specifically important when we're dealing with user input output as in network requests. Also, user input, users interacting with the system and reading, writing from disks, anything that is kind of considered asynchronous type of activity, something where the CPU needs to stop and wait for the user to do something. Or the CPU needs to stop and wait for some data to arrive from the database or the network. And so there's typically two ways for a program to do those kind of tasks. So to do IO specifically, we have the blocking or the synchronous style, which is easy to write. It's what PHP and Ruby and Python and most web, especially server side technologies, are doing synchronous or blocking IO. And in order for that to work, we need multithreading. Because if we stop one thread, we need another thread to be able to continue to serve user requests. But it's easy to write because you write everything sequentially. However, there's a memory overhead to that because every thread requires memory. And there's context switching. So when the CPU switches between the threads, so there's overhead there. The other sort of paradigm, the other pattern, is the non-blocking or the event loop style. And so that's single threaded. So everything runs in one thread, which means we cannot block the thread. We do get high concurrency, and we get low memory. So that's the win. And it's really good for UI, so user interaction stuff. That's why the browser uses the event loop pattern. And it's also good for IO bound services, things that are doing a lot of reading from network or database or disk. But it's not great for things that are CPU bound, because anything that's using the CPU is blocking other things that could be happening. So that's kind of the overview of the two patterns. And so JavaScript, as you know, is the second one. And so all the browsers and most JavaScript server-side frameworks are all event-driven or non-blocking. There are some exceptions to that, like historically, like really older engines. But in general, every JavaScript you ever write will be event-driven, non-blocking. Does anybody have any questions about that stuff? So what happens if you block in JavaScript? Well, there are some things that will block the thread, that will halt any other execution. And so alert is the most common, alert or prompt. And so we also have a synchronous style of XML HTTP requests. And it's rare, because that was probably a mistake in the API. They probably did not intend for that to be synchronous, or at least it wasn't a good idea. And then we have some stuff in Node World that are intentionally synchronous for various reasons. But I think the one that everybody's probably familiar with is the sort of alert style, where it blocks everything else that's happening. And so that's this one. And so that's like the page puts a dialogue up and everything pauses. You cannot interact or press a button. You can't even scroll. And so that's kind of a bad user experience. And so that's what we mean when we say a blocking interaction or blocking the thread. So how do we write concurrent software without blocking? So generally in JavaScript world, as you know, we use callbacks. And it's pretty straightforward. We just pass a function. And when the task is finished, JavaScript will call our function. And that would be, for instance, an on-click event. So you pass a function to the document, to the browser. And whenever a user clicks a button, then your callback gets fired, gets executed. Similarly, a network request. You're waiting for the network request to finish. And then the callback happens. So this is the callback style. We're going to read a file. This is more maybe server side. We read a file. When the file is finished reading, we have our content. So does everybody understand the callback style? I mean, that's pretty much every JavaScript program you've written. So what's the good things? It's a good low-level abstraction for concurrency, for asynchronous actions. It performs really well. It has low overhead. There's not context switching involved. And we can do almost any asynchronous task using callbacks. What's the downsides? Well, doing things in callback style is really difficult. So doing things in sequence is difficult. Doing things in parallel is even harder. You give up your constructs that you're familiar with, like for, and while, and try, catch. And those are things that people come to JavaScript World from PHP background or from Java or from almost any other language. And they don't understand why you can't do these things. And so error handling, as you know, you always have to check this error parameter. Things could fail during the middle of a network request. It needs to be handled. And your code readability goes downhill pretty fast. So systems become kind of hard to maintain when there's a lot of callback asynchronous code. And so this is kind of the example of the first one, which is like the, or sorry, the giving up the constructs that programmers are familiar with. This is your typical example. So it's like every day on Stack Overflow, somebody asks, what's wrong with this code? And can anybody see why this code will not do what the programmer expects? So here, we're defining an empty variable. And here, we're returning an empty variable. And then somewhere later, that variable will be set. After the network request comes back. But to a programmer from a non-asynchronous world, this is a strange thing. This is a strange thing. And so that's why people new to JavaScript are always on Stack Overflow asking this question. So callbacks, they add complexity. It's messy to chain them. If you need to do three things in a row, simple things. Like write something to the console, wait a second, write another thing to the console, wait another second, and write something to the console. That's like a lot of callbacks. So it becomes messy to chain things. And it's pretty hard to do things at the same time. And so this is your sequential stuff. And so this might be the most code you see on any slide. But I want you to get the idea of how the complexity grows when you're using this style. And we're not even doing any error checking here. This is like the simplified version. This is just taking three files. And we just want to total the number of bytes in those files. So we have to read three things from disk. So we're going to stat file one, add the size to the total, stat file two, add the size to the total, and do the same thing with number three. Every time we're getting more indented. And then eventually we have our total and we can do our callback. And so is anybody written code like this all the time? So if we want to do, so these three things, these three files, we don't have to do them one by one. We can just read all the files and then get the total number of bytes. So if we want to do it in parallel, it's a little harder. So this is kind of my naive version with no error handling of doing the same three files in parallel. So here we put the files in array. We four each. And we stat each file individually. The problem is how do we know when we're done? Because the answer might come back in any order. So we have to keep track of the number that are finished. And we've got to count every time one finishes. We've got to add that number. And when we've reached three, then we know we're done. So this is kind of parallelism in callback world. And then there's error handling, which we didn't even look at yet. So what do you do? We spent a lot of effort just checking if there's an error when we read file one, but file two has already completed successfully. How do we handle that? And if we look at the same example with error handling, it doesn't even fit on a slide. Because now we have to like, if there's an error, we need to log it and return. And that's not even good error handling. That's the minimum error handling just to log the error and just give up. And so we have to do it every time. And so we have readability issues. So when readability is this bad, systems become difficult to maintain, difficult to read. It's easy for errors and bugs in your code just to sneak in. So let's talk about promises. So we've kind of talked about callbacks. So promises do a little better. So this is basically a thin wrapper, a thin abstraction around callbacks. And so it gives us the chaining that we need, doing task one, waiting till it finishes doing task two, and continuing in sequential order. It gives us helpers to do things in parallel. It gives us error handling. And they're composable, right? Because the way a promise works is we can pass around a representation of a future value. So a promise is an object that represents what the value will be when the operation finishes. And so it kind of looks like this. So we read a file, and then we get two things. We get then and catch. So then we can, in a sense, attach a handler or a callback that gets executed whenever the data is finished. So it's better than callbacks. Like it may not seem so at first. It may seem more complicated. But it is better. And so it looks, if you look at this code, like all we did was take our callback and put it inside of them. But we still have our callback. But it's better because we can do some chaining with that. And so I'll give you an example with a very simple function that uses setTimeOut. And it just waits for 1,000 milliseconds for one second. And then it fulfills the promise. So it's the same idea as a setTimeOut but in promise world. So the idea is that here we want to just wait one second and log something and wait another second and log another thing. And so we don't want all of that nested callbacks. So we get a better, more readable pattern here. So at least we can see first we sleep, then we do this thing. And then we do this other thing. And then lastly, we do this final thing. So at least it reads in a very sequential order. And so the first thing you might notice is that we're returning something from inside our callback. So inside of our then handler, we're returning another promise. So remember, whenever we call sleep, it returns a promise that will fulfill or will finish in the future, resolve. It will resolve in the future. And so when we return a promise from in here, that's what allows us to call another then. And in the last one, we didn't return anything. So we're finished. So even this might be a little better than callbacks, but it's not amazing. But we get flow control. So when we go back to our example of doing certain things that we have to do in series, we have to wait for one to finish and then do the next, other things we want to do in parallel at the same time. And so that's really hard with callbacks, but it's easier with promises. So the example would be we want to fetch some data about maybe the user that's logged in right now. And then we're going to get back this user object. And then we want to fetch the friends of that user. And then we'll return that promise, right? So every time we call fetch JSON, we're going to get this promise. And so we'll return that promise. And then when that finishes, we have the IDs of all the friends. And so now, and this is where it becomes powerful, is now we can take these IDs and we map through them. So we go through each individual friend ID and we want to fetch the user details of that friend. And what I do here is I actually create an array of promises. So each time for each ID, we return a new promise. And since we're calling array.map, we get a map, we get an array of promises. And then we can call promise.all, which just waits for them all to finish, gets all the results and we can return that promise. And then when we're finally finished, our last then we have like all of the details for all of the friends in the order that they were provided in the same order that they were in this array. So promise.all is the magic that I want you to understand from this slide. So here we're doing something that would be really hard in Callback World because we're doing the first two things in sequence, right? They have like, we can't get the friends until we get the user, right? So we have to do some things in sequence but other things we can do in parallel. So that's the power of control flow with promises. So promises, even though they don't seem great at first, or at least when I first learned them, it seemed like a not much better than callbacks. Once you explore the chaining and the control flow, it is better. So error handling, we get a catch. And so we don't have to check every single time for this error. We can just attach one catch function at the end. And so exceptions will bubble through our code or bubble up the way we expect it to work in normal programming with try catch. So in this case, if we go back to our same user profiles example, I can just add one catch at the very end and grab that error. And we just know an error happened somewhere in one of these an error happened. All right, everybody with me so far? Cool. But we're still putting callbacks inside of then, right? I mean, we're still doing callbacks. It's a nicer way to do callbacks, but ultimately we eventually have to pass these functions. So can we do better? So what do we wanna do? Like we wanna just like have normal sequential programming like we're used to in other languages. And so here's what we want, right? We want to get our promise. We wanna somehow wait for the promise to finish and then we just wanna log the result, right? With no then anywhere. And so the problem is, JavaScript is fundamentally single threaded. So we can't block. And what I mean by that is, we can't just pause the program right here, wait for the promise to finish and then resume because we don't wanna block our entire thread and then you have the same problem as when you use alert and then the whole software is just not doing anything useful. So JavaScript is single threaded. We can't block, however, there's a special thing called a generator function. And so what that is, is that allows us to pause just one function and we'll go back to it later. So we're not gonna pause our whole program. We'll just pause this function. And so has anybody used or seen this generator syntax? With the star. So that's the special thing right here is the star. And then the keyword yield. So generators are not about asynchronous or event loops or they're not about doing concurrency. They're just about pausing stuff basically, right? So we get this thing called yield. We can just wait. So the idea is we just pause the execution of just this function. We wait for something and then later we will be resumed and we just console.log the string that says we're back whenever somebody has resumed us. So there needs to be something controlling this. Some function needs to resume this whenever the fetch has completed. So generators are pretty complex and I don't wanna spend a lot of time on them. I just want you to know that generator is the only thing that kind of allows this to work because we can pause our function. And it's important to understand it just pauses this function, not everything. So we take promises, we combine it with generators and we get awesome stuff. So this is the whole point. We get a single weight. So it's basically just a layer of syntax on top of generators and promises. And it looks like this. So instead of a function star, we have this async function but it's really similar to that function star, right? We use the word await and we put a promise after that. So await and then a promise and it always has to be that way. And then what that does is that await will pause it in the middle of executing this line we just pause the whole function and we just wait but we let the event loop do other stuff and then eventually when this fetch has completed this function gets resumed and then we have our result and now we can just console.log the result. So this is the whole idea of async await. A few things to remember. You can only use await inside of an async and it has to work with promises. So it's a win, it's a win because we get back our traditional constructs, right? We get back our for while, our try catch. It becomes readable. I mean, it's got some keywords in there that might seem strange at first but it flows like normal programming in any other language and normal thread blocking programming, right? And we get this kind of interop with promises so we can use any other promise library and use it with async await because it just uses promises. So the same example or a new example of reading a file here with async await is that here we just use this read file which is a function that we've kind of made up that presumably just returns a promise and then we just wait for that and we get our content and then we convert it to a string, we parse it with JSON and we log the result. So it just reads sequentially like you'd expect and the point of this slide is that now we have our try catch back. So two things could go wrong here, right? Something could go wrong with the reading of the file. The file doesn't exist. The USB stick has been unplugged. The permissions aren't there. So this thing could fail. The other thing that could go wrong is parse. There was invalid JSON, it just doesn't, the JSON.parse can't handle what the file had in it. So these two things could have gone wrong. And so this thing is going wrong in async world, right? Something is happening asynchronously while our function is paused. But this exception is just a traditional, normal, regular JavaScript exception. And async await doesn't care. The try will work exactly how you expect it to and it will send the error to your catch no matter which problem happened. So we can kind of not really think about async callback errors being different from any other error. And so we also get back our for loop, right? Things that are just normal programming constructs that have been around since the 70s and we understand them. And so in this case, we're combining just a regular for loop with async await and we get something really cool. So in this case, we just take a regular browser element and we're gonna loop through a hundred times and move it a little bit to the right. By increasing the left value by one pixel every time, we're just gonna increase it to the right. But we wanna do it every frame, right? It's an animation. We wanna wait in between the frames. And so we're gonna wait 16 milliseconds between every adjustment of the value of the style. So in this case, in like four lines, we've written an animation with a hundred frames that happen 60 frames a second that would be really difficult to do with callbacks. But because async await just pauses our function in the middle of a loop, it just does what we kind of expected to do and it's understandable. Does anybody have any questions about this? So it's just promises. And async function itself returns a promise. And so when we await a promise, our function pauses until the promise is ready or until it's resolved. So we can still use our normal promise stuff, right? So promise.all, which we talked about earlier, still works with async await. And here's an example of that. So here we're gonna fetch our, we're gonna get our user and we're gonna get our friends and then we're gonna map over our friends and create our array of promises just like before. But in this case, we're using await for both of the first things because we can't do the second thing until we've finished with the first thing, right? So we have to wait on those. But these promises that the fetching the friends, we can do those all at the same time. So we just create an array of promises just like before we call promise.all and then we await that. So we await them all to be done. So we just, so we, as long as the thing on the right of the await word is a promise, everything's cool. So the thing on the right needs to be a promise. And then we console.logit at the end. But the thing I want you to know about this is that this whole entire function because it has this async word itself returns a promise, right? So if I call the function get user friends, if I call this function, it returns a promise. Yes, yes you can. You can await another async function because it's just promises. So a few pro tips, don't forget to use your await. If you accidentally leave out this word because you're so excited that everything is like, you know, normal programming again that you kind of forget to leave out the await, it's not going to do what you think it does. So don't forget your await. Be careful about doing too many things in sequence if you can actually do them in parallel. So now that we have our for loops back that we love, it's very tempting to just put a weight inside of a for loop. And we have to remember that if we do that, it's going to pause that for loop every single iteration. So don't do too much in sequence if you can do it in parallel. And so the other thing is that using a weight in a map or filter function, probably doesn't do what you expect. And so I should show you an example of that. But the idea is that a filter function, right, a radar filter should take a function that returns a Boolean. But if you pass an async function, it's going to return a promise. And so probably not going to do what you think it does. So you just have to remember that. So map, it's fine to use, but just remember that if you pass an async function into map, you're going to get an array of promises, which might be what you want. And so even though it looks synchronous, also remember that your code has been paused and then started again later. So some of the assumptions that you might make about global state or global variables might no longer be true after your function has been resumed because things have changed in the world while you were paused. So user input, right, has been traditionally hard with Node.js. If you've ever written a CLI tool, you need to read some data from the user and then you gotta have a callback and then you gotta read more data from a user and it becomes really nested. And so this is just our final example. It's just that we can just create a read line function that returns a promise and we can now await it and write command line programs just like you would in Python or any other language. But we get all the advantages of Node as well. So you can use this today. Async await is in Chrome, it's in Firefox and it's in Node. For any of the other browsers, you should use Babel. But it's actually not that bad to use Babel because Async await is a pretty thin layer on top of just generators and promises. So most browsers support generators anyway. So there's not too much stuff that Babel needs to do to make this work. So thanks for listening.