Don't Imitate, Understand #2 - Promises, Async, and Await

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi this is don't imitate understand i'm tony alicea in this video we'll endeavor to gain a proper mental model of promises async and await in the javascript programming language in order to do that we'll first start by reviewing some fundamental things we need to be sure we understand in order to then understand promises async await and dealing with asynchronous processes in javascript in general for starters we need to know that functions in javascript are first class objects that means they can be assigned as values and passed around so we could write something like this a function that takes another function as a parameter in this case other fn does some work and then executes the function that it's been given so i could call run this and give it a function to then run i could do that again with a different function so this function again is being passed around it's a first class object i might also rewrite this using arrow functions like this but it's the same idea i have a function that takes another function as a parameter and runs it so when i call run this what's being passed is this function object and we refer to this concept and the function itself being passed as a callback we've given it to a function and then it calls that function in return and this happens again with a different function object after it's done and it actually runs we would get output that looks like this the run this function's been run twice and each time it ran the callback that's the first concept the second concept is how javascript is actually executing its code under the hood under the hood of the javascript engine is an execution stack on the execution stack are placed execution contacts where your code begins is the global context and as functions are called they're placed on the stack if function 1 called another function let's say function2 that would also be placed on the stack and when that function is done then that execution context is removed from the stack as part of this concept also is how javascript is running each of those individual execution contexts if we looked inside one of these functions we could see that essentially the javascript engine is executing each line of code individually one at a time because javascript is synchronous and single threaded which essentially means that it's doing one thing at a time but we know in practice that when we're writing javascript code there's often multiple things happening at the same time how is that actually going on the javascript engine provides the idea of a queue essentially a spot to put notifications that things outside the engine have occurred things that in your code you might be interested in and when we say outside the javascript engine it's because how the javascript engine is generally used in practice in reality this entire concept sits inside the javascript engine which for most engines is c or c plus plus code and those engines are in turn embedded in other systems also usually written in c c plus plus code like internet browsers or nodejs and that's where these other things these other processes are running that might be happening at the same time that the javascript engine is processing its code and then might notify the engine that some work is done for example a common feature in browsers and in node.js is the idea of a timer something that can count down and then do something once enough time has passed now this concept does not exist inside javascript itself it's not part of the javascript specification or javascript engines but since javascript engines are usually embedded inside a larger system that larger system can give features to javascript that it normally wouldn't have for example for years developers have used something called set timeout but set timeout doesn't exist in javascript it's not inside the specification it's a feature made available to javascript from whatever system it's embedded inside of usually the browser or node.js and then we can use the concept of first class functions to specify a function that should run when that external process is completed and when that process is completed a notification of that can be placed on the queue and the javascript engine then knows what to do if we zoom back in once this other code has been completed the javascript engine can look at this queue understand that it needs to be processed and run the associated callback function in modern javascript engines it's a little bit more complicated than that in that there's actually multiple cues depending on what might have greater priority but from a mental model standpoint this is enough to understand what's going on and again this is our callback now once we start writing code like this where we're initiating the use of a feature providing a callback function and then doing something inside that function we begin to find ourselves in a bit of a quandary a bit of a difficulty when it comes to coding because i might have several things in a row that require waiting for that external process to complete and i might get code that looks like this let's imagine that i set a timeout when that timeout was complete i had to go out to the internet and get some data like a person that's going to happen through the browser for example that's not part of the javascript engine so javascript requests that it's going to be another process external to the engine which means we're going to wait again and then perhaps once we get that person information back i have to go out to the internet again and get another set of information like git log and so we end up with this nested set of callback functions this actually gets really difficult to deal with just from a writing code and debugging standpoint from looking at it and understanding what's going on especially when it gets big and there's lots of logic involved in fact sometimes this coding structure is called the pyramid of doom because if you look at it sideways it kind of looks like a pyramid there's a couple of other problems with this coding structure let's suppose that we process the event and the callback runs now the event is over but what if we had multiple callbacks that we wanted to run when that event completed where would we specify that another problem is what if the event is processed it's handled and then later we say in some other function whenever that process completes i also want to handle it but the process is already completed so what do we do these are problems not solved by this coding structure but i bet if you stopped and thought about it you could start to imagine ways you could code it to be able to handle those problems it's not that javascript can't handle those problems it's that this basic simple idea of callbacks within callbacks doesn't handle that idea very well we need a better concept a better coding approach to dealing with asynchronous events and that's where the idea comes in of a promise a promise is a standardized approach to dealing with asynchronous events and callbacks now sometimes we hear things said that sound kind of complicated kind of deep like javascript has promises now or some feature returns a promise but that's nowhere near as complicated as it sounds it's really just an idea let's illustrate with a simpler idea let's suppose that around the world javascript coders everywhere we're dealing with people that is to say data about people and so these objects and data represent a person of human individual but many javascript coders were doing their own implementations of very similar ideas maybe one uses a person object another one uses a human object another one uses an individual object they all basically do the same thing but they're not quite the same and that makes our life a little difficult so everyone gets together and says let's come up with one specification for what a person object should look like and we'll all use that same person object when we do that that makes all of our lives easier and we can write tools and utilities and share code and everyone's life is better then the folks that write the javascript engine say great it's in the specification so we'll add it into the engine we'll make this object available and so people start saying javascript has persons now or some feature returns a person it doesn't really mean that javascript can do something new that it couldn't do before it means that there's now a standard that's available in javascript so that you don't have to write it yourself and that we all know we're using an object that is the same object it's really a simple and useful idea in the exact same way promises are an object that represent an idea a promise object represents a future value a value we know eventually we're going to get but we may not have yet it encapsulates this general idea that we've already seen when it comes to the javascript engine being embedded in another system that provides other features so that system provides a feature and it provides some hook for javascript coders to request the use of that feature and a way to run a function when that feature has completed its work when that work is completed we assume that we have some kind of value maybe some data coming back from some other system a true or a false whatever the case is we have some value in the future and a promise represents that value but it also wraps up the idea of requesting the usage of that feature and handling it when the feature has completed its work and again these aren't things that the javascript engine couldn't already do but coders wanted to encapsulate this idea standardize this idea of an object that represents a future value and there were other implementations out there in the past but then it became part of the javascript specification and javascript engines implemented a standardized version of the idea a standardized object and that object is a promise now to understand promises and what they do we're going to start by coding just a little bit the first half of our own version of a promise to get under the hood a little bit and understand the ideas then we'll use the implementation of promises in javascript to understand more then we'll use that to understand async and await so let's write a little bit of code that shows how a promise object is supposed to work now we'll say right away don't use any of this code we're about to write we're going to focus on simplicity so we understand the ideas but that means this code will not be ready for use in production and you don't really need it because a promise object is already available in javascript so let's write the beginnings of our own promise object to start with we think about what it's representing it's representing a value that comes back after work is completed so there's three states that we want to think about there's the state where the work is pending the state where the work has been fulfilled that is it's done and completed successfully and the state where the work tried to complete but couldn't there was some problem some error and we'll call that rejected the promise then will be in one of these three states we're waiting for work or pending the work is done and we have a value successfully which is fulfilled well the work did not complete successfully and we probably have an error message which is rejected now let's create our promise object i'll call it custom promise and what we'll give it is an executor function what does that mean it means the function that will actually do the work that we're talking about that will request the data from the database or run a set timeout a promise object doesn't actually do any of the work that we've been discussing a promise object wraps up the idea of waiting for that work to complete and then figuring out what to do after the work is completed it's up to the coder who's using the promise object to actually write the executor function that does the work and then give it to the promise and then the promise will run it the first thing a promise needs is its current state we'll start that off always as pending we're waiting for the work to complete then here's the big important one that value that we're waiting for it starts off as null but we know that eventually we'll have some kind of value or perhaps an error message then we'll deal with one of the first problems we talked about perhaps i want more than one handling function more than one callback to run when the work is complete so i'll have an array of handlers and maybe i need more than one function to handle things if something goes wrong an array of functions that get called if there's an error i'll call these catches now we add a very important function we'll call resolve that means the work is done and i have a result a resulting value this function will get called by the executor so we'll give this function to the executor and that function the executor function the function written by the coder using the promise will call the resolve function that's on the promise object and give it that value that it just received inside the resolve i'll do something simple i'll stay if we aren't pending anymore then i'm not going to resolve that means that promises are designed to deal with what's called one and done operations something that's only done once and then it's finished so if it's not pending anymore there's nothing more to resolve the value will not change once it's been set and that helps us make sure that our code works the way it should if however we're still in a pending state well now we'll say because you called resolve i assume that the work is done it's been fulfilled and i'll set my internal value to whatever result the executor function gave to me so now the work is done and i have a value and the last thing is can you guess i need to take all the handlers and run them so i'll say for every handler function in the array i'll execute the handler function and i'll give each of those handling functions that value that just came back so now this does the work the work is completed the promise now has that future value set and all the callbacks are run we can do something very similar if there's an error i could say i have a reject function that gets an error i'll never call it if the promise is already finished but if the promise hadn't finished yet i'll say well now you've told me that it's finished and i know that it's been rejected there's a problem and i'll set my value maybe to the error message and i'll say catches and i'll call each of the error handling functions and give it that error message now here's one more function and this is one you see a lot when using promises dot then dot then takes whatever the callback function is and does one of two things here we're going to again solve one of the problems that we talked about if we call dot then and the promise has already been resolved that means we already know the value whether it's fulfilled or rejected then if the state is fulfilled then we don't have to wait for anything so we'll just execute the callback immediately and we'll give it the value that we already know we have if on the other hand we're still waiting then we'll simply add the callback to the array of callbacks or handlers that we're going to execute when it's done now i won't do the work of figuring out what to do if there's an error but you get the idea we're either executing the callback immediately over adding it to the array of callbacks which will be executed when the promise is resolved when the resolve function is called the last thing to do is what always has to be done when the promise is created we run the executor function a promise represents a process that's already running so when we create the promise that actually starts the work the creation of the promise runs the executor function that we give it and the executor function that whatever coder is using the promise object writes should expect a resolve and reject function these two functions are passed to the executor function so it can use them when the work is completed or when an error has returned and that's actually enough to demonstrate this first half of what a promise object is meant to do let's try using it i'm going to create my actual executor function a function that does some work i know that i'm going to be given a resolve and a reject function when the promise executes this executor function that i'm writing so now i'm going to do some work i could just resolve it right away to some value to say the value is done but we want to demonstrate what happens when you're waiting for work to be done so i'm going to use set timeout i'm going to give set timeout a function to run after one second what that function will do is simply call resolve now it might do in the real world a bunch of other work in order to get this final value and then call the resolve function here i'm just going to call it right away and say hello world so what happens is when i create the promise it will represent some future value and i'll give it my executor function now remember what will happen when this promise is created i've given it an executor function the state will be pending and the value will be null then inside my executor function i call the resolve function that was passed by the promise because the promise immediately executed my function and gave me the resolve and rejects so that i can call them so i'll call resolve and give it the value hello world which will end up as the value inside the promise object and then every handler that i add using dot then will be run so let's add some handlers i can say some text dot then because some text is the promise and i'll say give me the value that was resolved and i'll just say let's output that to the console now remember this is adding the function that i passed in to an array of handlers so i can do this again and i could say a second log function and remember now i'll just have handlers i'll have two of them inside the array and both will be called and i could even add another handler after we know the promise has been resolved remember we said that set timeout after one second will resolve the promise so i could say after three seconds knowing that that's going to happen after the promise is done to go ahead and do this again and this should still work because when that handler is added via dot then the state will be fulfilled and it'll just call it using that hello world value that we've now resolved so let's run this and see what we get the first set timeout which resolves then the next set timeout which adds yet another handler and all three handlers run successfully using the resolved value why does this work one more time we created a function our executor function that does work and expects these functions from the promise we call the resolve when our work is done this function is immediately executed by the promise when the promise is created every time we call a dot then we're actually adding a handler to an array and so that array is simply used when we actually have a value that we resolve any handlers added after the resolve is complete through this.then function is also simply executed but it's executed right at that moment because there's nothing left to wait for we already know the value i hope this makes sense a promise object represents a future value for a process that's already running and dot then lets us add handlers for when that work is complete and we know what that value is we haven't added all the error handling and we haven't tried to make this robust but this is the basic concept it's a better coding approach to dealing with callbacks and with promises in javascript this coding approach is standardized now inside javascript engines the included promis object does get a bit more priority than some other external processes but this is basically what it's doing the exact same thing the second half of what a promise does is where our coding structures get really interesting what if i have a sequence of asynchronous processes and i'd want to avoid that pyramid of doom what would be really great if the dot then function did something really fantastic if it returns a promise why is that great because if my callback also does something that requires us to wait for a returned value i could then chain a sequence of thens i could flatten the pyramid all right let's see what that really looks like by using the actual promise object that comes along for the ride in javascript so here's the same idea but using the promise object that's built in to the javascript engine i have my executor function that's doing some work we know that the promise will run this function and when it does it will pass in its own resolve and reject functions that i can then use i'm going to set timeout and resolve the promise with a value in the real world this could be a success message or some data i'm just going to say hello world then i create the new promise giving it that executor function notice i don't have to do much here the promise objects is already available to me and then i use the dot then function to add a handler and when i run it in the browser after a second the handler executes using the resolved value hello world so we understand how this works now what if i had another situation in which i wanted to do another piece of asynchronous work but i wanted to do it as a result of the first let's suppose that i have another executor function i'll just call it do other work and it will resolve to a different string maybe how are you but it will do it after three seconds now i want this function to run but i only want it to run after the first function is complete after that first promise resolves so how can i do this well first we can understand a major important feature of the dot then function in the built-in promise object if my handling function returns a value i'll return a string here what does the dot then function return again my function returns a string to find out let's set a variable equal to the return value of dot then and what you'll see is that i can then do this i'm going to go ahead and do a new dot then and i'll output to the console the value that's returned what do you think we'll see my function returns the string the dot then function returns another promise which i then attach a dot then to a different promise and there it is both dot then ran now because of javascript's chaining ability i can write this differently i don't need to set this value i can simply attach the next dot then to the return value of this dot then but it's the same thing some people get confused when they see this they think that two handlers have been attached to the original promise but it hasn't one handler has been attached to the original promise and a new handler has been attached to the new promise returned from.then so the built-in javascript promise object has a feature when my handler returns a value it wraps that value up in a promise so that i can continue this approach to coding my responses to the asynchronous processes completing even though this value wasn't created asynchronously but even better what happens if i return a new promise with that other executor function the one that i wanted to execute after the first executor function completed what do you think should happen the second function executes after three seconds so three seconds after the first one completes that other handler completes and we see both results hello world and how are you what's actually happened is very clever my function returned a promise the then function always returns a promise the promise that it returns in this case is connected to or synced up with the promise that i returned in other words when the promise that i returned resolves this new promise created by then resolves and will have the same value as the promise that i created resolves to it effectively acts as if i'm adding a dot then to this new promise it's not quite the same because it's always adding to a new promise created by the then function but effectively this is what happens so inside every dot then i could have a new asynchronous process occur return a new promise and the promise object's code will make sure that each dot then is only run after the promise resolves so we can have a sequence that works properly without getting into a pyramid of doom we flatten the pyramid we would simply have a sequence of dot bends each dot then attached to the new promise generated from the previous function's return now in reality you're not always going to be calling the resolve function yourself remember the point of having a standardized promise object or one of the points was to be able to have utilities and tools that work using the promise object and often you'll be using those utilities and tools which make use of promises but this helps you understand what they're doing let's take a look at a slightly more realistic example in this example we'll use a feature called fetch fetch isn't a part of the javascript specification it's its own specification it's meant to be used in javascript but implemented by those systems that embed the javascript engine inside of it like a browser fetch goes out and does the work of going out to the internet and getting a file or some data or whatever the case is in this case we've made a little json file called video it's an array with one object in it with a title fetch will go and get this information and fetch returns a promise but now we know what that means the object built into javascript that wraps up a concept of how to deal with asynchronous events and callbacks i'll have a dot then and i'll be able to use a special type of object that comes along with fetch for handling the response a stream of data and on that response i can call.json which parses that json string and converts it into javascript objects and arrays in memory and guess what dot json returns you guessed it a promise now remember this is just shorthand here using this arrow function for return response.json with this execution so the shorthand function is returning the results of response.json which is a promise so i can call another dot then when this function is executed that means that i'll have that final in memory result when the json is parsed and i'll output that to the console what does that look like just what you would expect an array that has one element an object that has a property called title notice i haven't had to call resolve that all happens inside the fetch feature but we understand what's going on and how to use it and it keeps my coding structure flat and easier to read understand and deal with so is that the end of the story no because javascript has another way to code to write to deal with asynchronous events and callbacks it depends on promises it relies on them but it makes your coding life even easier and it's called async and weight so how does that work well you know before we do that i'd like to throw in one other piece of information a thing that you hear when you hear promises async await being talked about that sounds really strange but it's actually a very simple idea we've already seen that one of the points of having a standardized promise object is being able to have this coding structure of dot then dot then dot then sometimes you hear people talk about thenable objects so what is that a venable object big word alert then able an object that's thenable simply means basically an object that has a then function and this has to do with coding something that goes beyond just the built-in promise let's say that we have a set of our own objects or we're using a utility that has its own set of objects that act like promises or use them we'd still like to be able to use this same coding structure suppose we have some promise and we add a handler on it using dot then we know we can chain these together but let's suppose in reality we're using some custom object that isn't really a promise but we'd like to keep using this.then.then.then structure essentially what we're saying is if that object follows the same pattern of how a dot then function works it's then-able so we can continue to use that same coding practice and it's really no more complicated than that all right now that said and that out of the way what about async and await so here's our similar code again i have two functions that will do work they'll resolve a promise in each case and i have a new function that i've written called do all the work but we have a new keyword in front of the keyword function async inside this function we create our new promise and then await that promise with the keyword await then output the results we'll do the same again with the do other work promise await that result and output it then we call this function and output to the console that we're done now the council outputs considering these are asynchronous processes should result in done being seen first then after one second hello world then after another second how are you if these two promises are resolved in order if the second promise waits for this first promise to be done when we run this that's exactly what we see so what just happened well we know what just happened we could already accomplish using promises and dot then using a coding structure that we could even make ourselves we certainly couldn't add these new keywords to javascript so these have been added as part of the javascript engine therefore async and await are something that's called syntactic sugar big word alert syntactic sugar are features designed to make writing code more efficient clean or understandable but in reality don't let you do things that you couldn't already accomplish before in another way so while async and await are new features new language features new syntax that we can write they're just syntactic sugar they're there to help make our lives easier to help our code be easier to write and debug and to understand other people's code now what's really happening when this code is run if we think about our execution stack the global execution context is executed then that async function is called and when an async function is called if we think about what's happening as it's executing it it's running through those lines until it reaches the keyword await so the keyword async tells the engine that we intend to await at least one thing inside this function what are we awaiting we're awaiting a promise remember we said that async and await rely on depend on promises as a concept and this is why a weight will rely on what it's awaiting to be a promise now technically you could await a different value but it will still ultimately essentially be wrapped up in a promise just like the then function does when the engine reaches this await keyword then the execution context is paused you can think of it as kind of being set off to the side and regular function invocation and execution continues and that is going to sit there until the promise being waited for resolves either fulfilled or rejected so we imagine perhaps some other function being called another work happening and then eventually the promise that the await keyword told the engine we wanted to wait for finishes its work when that happens that paused execution context goes back on the stack and at the point we were awaiting function execution continues only now we have that value that the promise represents and so the rest of the execution of the function can continue so when we look at our code here we can see that we're awaiting this promise to be resolved that means that the code after it kind of acts like what's inside the handling function passed a dot then we have another promise that is again awaited so this function will pause twice waiting for both of these promises to resolve and in the proper order so why is this so great because it takes our code that's dealing with asynchronous processes and makes it as simple to look at and write as synchronous code is to write and this as coders makes our lives easier this really is a terrific feature of javascript now i'll put a small caveat here that async can await depends on promises and that means that any code that you're dealing with that uses async functions and a weight also need to be smart in dealing with promises so one of the dangers of syntactic sugar is that you can just throw on the keyword and think it will work just like you expect but that's not always true so be cautious if you're dealing with some code that uses async and a weight remember what's actually happening under the hood that the function execution is being paused and the workflow continues to the rest of your javascript program that can help you deal with some unexpected behaviors especially if the rest of your code is not dealing with or waiting on promises and which one should you use promises or async await well the answer is if you're using async await you are using promises and in some circumstances you'll use both async await is designed to be much easier to read much easier to write so personally i lean toward using async and await as much as i can but understanding they're just a useful way to write code that deals with promises we also haven't touched every single aspect of the promise object and there's other useful functions available as well as dealing with errors via dot catch or try catches inside an async function but i hope this video has helped you get a proper mental model of what promises async and await really are to help you to write your code and debug your code when you have problems and that's it promises async and await in the javascript programming language this has been don't imitate understand and i'm tony alicean if you like this video please subscribe and also check out the descriptions in the video to look for coupons to my full-length courses happy coding
Info
Channel: Tony Alicea
Views: 27,245
Rating: 4.9646497 out of 5
Keywords: JavaScript, Async, Await, Promises
Id: fyGSyqEX2dw
Channel Id: undefined
Length: 42min 43sec (2563 seconds)
Published: Wed Dec 09 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.