Transcript from the "Challenge 10: Solution" Lesson
>> Mike North: We're going to go through the solution to the last exercise, whereby we implement this task function that will treat the contents of a generator function in such a way that we have something that looks looks almost exactly like async and await. So we can run npm start autocomplete-4.
[00:00:27] And we've got our task function here. Before we actually start this exercise up, I recommend that when we kick this last one off, that we try a couple of smaller examples that would give us signs we're on the right track before we went for the full blown async and await.
[00:00:47] The first step I'm going to go through here is an effort to show that promises are being chained in sequence together instead of sent off in parallel, as they would were we just writing synchronous code. And if you have a couple of fetches that aren't chained together, those will just all go off, and they'll come back whenever they come back.
[00:01:12] An easy way to do that is to have something like this, where we have await for 600 milliseconds, and then await shortly thereafter with 300 milliseconds.
>> Mike North: Something like this.
>> Mike North: So effectively what we expect to see is, if we were to do these in serial, right, chain them together, we'll see the 600 finish first, and then the 300 will happen after it.
[00:01:53] If we were to run them, start them both off at nearly the same time, because the 300 is gonna wait for a shorter period of time it will be the one that finishes first. So this gives us our first sign that we're chaining things together. Now, we're not worried about passing anything into the iterator at this point in time.
[00:02:11] We will tackle that as the next step. We'll worry about the values of these promises is in the next step. We're just worried about sequential order and that we're assembling this chain correctly. So first, we're gonna implement our solution here. And let me see if I can make this even clearer.
[00:02:31] Wonderful, so what we're gonna do is, we've got this iterator, and the easiest way to do this, we could do a looping approach, or we could do a recursive approach. The benefit of recursion is I can use things like return to control, an early termination of a particular branch of code as opposed to having to worry about a lot of very controlled branching and use of something like the continue statement inside a loop.
[00:03:02] So I'm gonna opt for recursion here. So I'm just gonna have a function here called nextStep(). We'll just call it that. And this basically indicates an attempt to grab something off of the iterator and deal with that. And we'll begin, so we'll define it, and then we'll basically invoke it for the first time.
[00:03:30] And so let's make sure that we can get that first function out, the first promise out, sorry. And let me rename this to just iterate it for ease of typing and ease of reading. So, we'll say, p = it.next(). Or actually, let's say, we call this an iterator item.
>> Mike North: And so, we need to worry about two things. Number one, this has a value. This has a value and it has an indication of whether it's done or not. So if it's done we'll return. And let me just log something out in the time being.
>> Mike North: And we can rename this actually, itResult.
[00:04:31] Just to align with the type.
>> Mike North: Okay, and this point next step is done. So basically if we pull something off and we indicate that we have overshot and let's pretend, for now we're just dealing with yields, we did see that when we used return in a generator function, done and a value came and the same object.
[00:04:54] In this case we know that, when done is true that's an indication that we've already dealt with our last value, so this ensures that say if we have an iterator of size 0 right? There's no items in here, we're done immediately. We're not going to infinitely recourse, so this is sort of our stopping condition here.
[00:05:19] In this block of code we know that we're not done yet. So we have a value.
>> Mike North: So, there is our value. And I'm not gonna actually, sorry, changing my mind here. I'm not going to presume that it's a promise. I've got an, izPromise function up here, that should be useful.
>> Mike North: Great, so the value is not a promise. We're not gonna worry about it for the time being.
>> Mike North: If the value is a promise.
>> Mike North: We're gonna say value.then. There's my promise result. And we're gonna then call nextStep.
>> Mike North: We actually are not even gonna worry about this for now.
[00:06:44] We don't need to worry about what it resolves to. So let's see how this works.
>> Mike North: Okay, so here's what's happening. First off, we did see that the 300 did not beat the 600 to the finish line. This indicates that we're chaining things together in a sequence. So the first thing that was yielded is a promise.
[00:07:10] The second thing that was yielded is also a promise, and then finally we hit this done branch. So this is our first thing that we want to see. That we've got this sequential chain of things working for us here. So then the next thing we wanna worry about is a value, right?
[00:07:30] So for this, I'm gonna deal with fetch. Fetch gives us a nice, if you've ever worked with this, gives us a sort of two step asynchronous process of getting from a URL to get some JSON to the actual JSON itself. So that's great, cuz it's two steps and this will help me ensure that I can do one and then the other.
[00:07:49] But this is a nice value base thing that will help me experiment with that.
>> Mike North: And I'm actually gonna borrow. Sorry, I'm jumping around here.
>> Mike North: Okay, so, I gotta start my app up here, but I'm gonna use this Google Places API.
>> Mike North: As a means for testing this app, so just making sure this URL works.
[00:08:31] And in fact it does, so this is the same thing that we've been using. But I'm just going to paste that URL in here. We're proxying it through local host currently and that should give a result and that should give us some json.
>> Mike North: Alright, so now it's time for us to deal with this data part of things.
[00:09:16] Now let's see what our existing code, how it handles that. Okay, so fetch is not defined, interesting.
>> Mike North: Because I'm running it in node. Okay, no worries, we can do it some other way.
>> Mike North: No problem.
>> Mike North: So you could've added node fetch here, I'm just gonna create a new thing here like function, valuesoon and then return new promise.
[00:10:03] Or we can even do this, wait, I can just use wait in fact. Wait for 500 milliseconds then return first, so that should be first.
>> Mike North: If we see that, we know we've captured it effectively. And this we'll just do a second wait for exactly the same amount.
[00:10:29] This will be second-
>> Speaker 2: You have to yield those [INAUDIBLE]
>> Mike North: Yes we do, thank you.
>> Mike North: Great, okay, everyone agreed that this will give me some indication that I am effectively completing that assignment operation. This will make it start to look a lot more like it's syncing away.
>> Mike North: I disagree.
>> Mike North: Okay, so first undefined, second undefined, we're not seeing what we want yet. All we have to do here is deal with this promiseResult. And we know that ultimately, we want when we call this iterator here, we want to push the value in of the previous promise as we pull the next promise out.
[00:11:19] Think of it that way, it's almost like a trade. I will give you what just resolved and then you chew on that. Run your code then give me back the next promise once you hit a yield, so think of it that way. I push something into next and then that's almost like a function and location.
[00:11:41] But it's just gonna run and tumble into the next yield and what I get out is the next promised to wait for, right? So, I have to put a value in here..
>> Mike North: LastPromiseVal and ultimately that has to come into this function and the good news is we can pass it in like that, right.
[00:12:00] PromiseResult is implicitly of any type. So, here's where type script it does a little bit of a better job than we're able to do right now without going nuts with the type definitions. I don't have any particular knowledge of the types that these different types that these different promises resolve to.
[00:12:16] You could use generics and constraints, I'm sure there's a way to do it. In fact a wait operating the way it does is probably proof there's a way to do it. That is like type definition back flips that I'm not prepared to do for us right now. So for the time being, we're gonna say it's okay for this to be in any type.
[00:12:42] All we really want to worry about in terms of the type is the last value, it's the last value
>> Mike North: So now if you run this in this, we're gonna pass in undefined.
>> Mike North: The reason we do that, think of that as like that's the first time we kick things off.
[00:13:08] So it's a little bit of an edge case, but it's the first time we pull this iterator. We're not completing an assignment operation. We're not paused immediately on the right hand side of an equal sign and providing a value to complete that assignment operation. This is just the first time.
>> Mike North: And it's okay to pass that in.
>> Mike North: Looking good, so we've got first first, second second. We're recognizing that there are promise. I'm actually going to handle this here, so that in theory, let's say we were using this. We wanted to gather these items and sequence for some reason.
[00:13:55] We wanted to yield a synchronous value. We can handle that, we can make it so if you accidentally yield something and it's there. We'll just yield the value right away, it's not problem. Oops, sorry, it popped up tot he top. Yielding the value right away as opposed to waiting for the promise to resolve like we have nothing to wait for in this case.
[00:14:18] So if you just yield six, that's totally fine. So if we did something like this-
>> Mike North: This should not throw us for a loop. Now, I didn't ask you to do this, but this is just a good complete solution.
>> Mike North: Well, I should really pass in\g third and we should see that values basically pass straight through yield.
[00:14:52] Let the sticks\g pass through. In any case, looking pretty good in this situation. So let's remove our console logs and run our generator function here, we left out one thing here. Ultimately, we want this to resolve with the last thing that was yielded, so let's do that. LastVal and we're gonna say, console.log, lastVal, we'll just say that's what we ended up with.
[00:15:30] So essentially, the use case here is in an async function. You're gonna return something at the end or you're gonna yield one final value and that's what you're interested in. That's sort of what you were building up to. It's the same way that ultimately our arrays\g and functions work.
[00:15:49] And in this case we're currently gonna return undefined. Should be undefined.
>> Mike North: It never even resolves, interesting. So there's our resolve, we've never invoked it here yet. So now, it's our job here, instead of just saying return, we have to resolve the function.
>> Mike North: But we're not passing anything in.
[00:16:15] So I'm not gonna bother running it again in to show you that like we need something here. What we can do is something like this. We can outside the disclosure say let value-
>> Mike North: And then in here-
>> Mike North: So basically by the time we invoke this again we will still be holding on to the previous value that was most recently admitted.
[00:16:50] Is that make sense that what the change does? I pulled it outside in to the outer scope, so that when we turn around, when we come back through and execute this function one more time, value will still be there waiting for us. This is one of the benefits of dealing with the generator function.
[00:17:08] Our execution context is preserved.
>> Mike North: So this has an implicit any type, we really want this to have a value of type. So we don't have the type safety we're looking for.
>> Mike North: We'll just do it like this?
>> Mike North: That's coming implicitly.
>> Mike North: It wants a st.
>> Mike North: There we go.
>> Mike North: Some ugly stuff going on here, and again, the goal here is not to use this piece of code, but to prove that generator functions lead up to async and await. When we run this, we should see that the most recent thing, which should be third, that is in fact what is omitted from this giant promise.
[00:18:21] So effectively, we've been able to say, here's some asynchronous steps we can go through. And then at the end, like, we can return this last thing and it works. The last thing we wanna check is this case.
>> Mike North: I wanna make sure I got third out. All right, I wanna be able to return as well.
[00:18:46] Let's see what happens. We're just seeing that because of an add-on book. So I think I may get second, and I do. So the reason is we basically handle the done case first. We handle the done case first. So what I can do is say, if we're done and yes.
>> Mike North: And the value is-
>> Mike North: Undefined. So basically, we're taking advantage of the fact that subsequent pulls on this iterator will give me done undefined again. So we're saying like, look I'm willing to tolerate done and term one more time. Or at least until you give me that undefined value there.
[00:19:43] This would also save us in the situation where we yield undefined but we're not done yet. So we do need both of these conditions here, right? Not that I can explain why you've wanna do that but-
>> Mike North: Now, we handle the return case as well. This makes sense to everyone?
[00:20:04] It's a little bit involved, I definitely took a couple of more lines than 20 just for clarity sake. Let me remove this, and let's drive our code and see how it works.
>> Mike North: And it works exactly as it did before with async and await. However, this time, we've implemented basically the exact same thing by way of a generator function.
[00:20:33] So know that when you're using async and await and you're in an async function, you're really under the hood yielding promises and waiting for those promises to resolve and then pulling the next thing in the iterator. That helps you understand the nature of this pausing local execution, and then entering back into this function here.
[00:20:59] So I'm now gonna take this one step further, and now that we have our own guts of this async in a wait apparatus. I have the ability to fix something that has been bugging me here, and that is that if I type, like this, you see we get a little flash of the previous results first and then the next results coming in.
[00:21:25] Really, all we need to do here in order to make this a little bit better is first, let me state what we're seeing here. When a key stroke is hit, and we begin a new search, we basically send a request out. And say when this response comes back, your instructions are, update the state of this reactive component.
[00:21:46] And it so happens that we're lucky that this is a consistent API, and usually the last one that we dispatch is the last one to come back in. And so now we're seeing, like, that this was the last to land, and so it's what remains on our screen.
[00:22:01] We really have no guarantee that would be always true. So what we want effectively to do is when we see that there's something already in progress and we're about ready to dispatch another request, we want to say I'm not interested in any responses from whatever was just pending.
[00:22:21] So let's add that in.
>> Mike North: What we wanna do is define a new interface here called-
>> Mike North: CancellablePromise.
>> Mike North: And there's a promise like type which basically if we peek at the definition of what this it just says it has a dot then but that's about it, that's all it has.
[00:22:55] It has the right constructor and dot then, so there is not much to it, we're just gonna add one thing to this interface.
>> Mike North: A cancelled property. And our task functions now going to return CancellablePromise, and that means that in our, down here, when we return this promise we have to say-
>> Mike North: P.cancelled = false.
>> Mike North: Or sorry, we'd have to do something like this, p as, no, doesn't like that
>> Mike North: New promise, we can go down here as-
>> Mike North: All right, we can do our little two line thing.
>> Mike North: I don't believe we can just unpack this object.
>> Mike North: Cannot be converted because cancelled is not there. Hmm. How are we going to do this?
>> Mike North: We can do a,
>> Speaker 3: [INAUDIBLE] Object ...P canceled to S.
>> Mike North: We could try. I'm not sure that will work. ...p.
>> Mike North: Is that gonna be? That might work. Lets see.
>> Mike North: Wow, if that works, then I love the structure aside. I love that spread even more than before. Canceled,
>> Mike North: Yeah, that's not assignable because it wants to catch, there. All right, some shenanigans required, brace yourselves.
>> Mike North: I know what I can do.
>> Mike North: That might work.
>> Mike North: Yes, so momentarily it's not gonna have that but we're gonna say cancellable promise, canceled equals false.
[00:25:52] All right, so the benefit here and this is actually the better way to do it, I can do it right up here.
>> Mike North: Perfect. Okay, so, now this cancelable promise, if we receive this as a cancelable promise, we can assume it is a cancelable promise. We have to kinda push it up stream here, I'll save this task, autocomplete that's where were going next.
[00:26:23] So the task is gonna return a cancelable promise and hopefully, I need to export it from task. Export the interface.
>> Mike North: So now, it's gonna say import.
>> Mike North: Okay, and this returns the correct kind of promise. What is this complaining about? It's not a valid function.
>> Mike North: [SOUND] That's fine.
[00:27:02] We can just do that. Make that happy. So now this returns the cancelled promise, and then we can go to auto. We can go to where this is used, which is our PlaceSearchContainer, and this is gonna use our autocomplete here. Await is still fair game because anything that returns a promise can be awaited, right?
[00:27:29] Except what this returns is that cancellable promise. Why is this still.
>> Mike North: I'm in the wrong exercise version here. Sorry. Four, there we go. This makes more sense now. So here,
>> Mike North: Okay. So now down here we can do something interesting. In the event that this thing is called, let's make sure our app still works first.
>> Mike North: Good, still works. So in the event that we call begin search and there's an existing search underway, and we can tell because we set existing search back to undefined once the problem resolves. Like if existing search on this state of this component is defined. Because we only ever set it to a promise and we have type safety around it.
[00:28:39] We can basically cancel it as we kick off the next one. And then once we do that, now we'll go back to task and we'll figure out what to do about that data. But we're sending in a signal where basically that promise has a property on it now, that we can use to determine whether we should care about any subsequent results.
[00:29:00] So here, we'll say if this state existing search,
>> Mike North: Just something like that. We're basically like flipping a bit, saying, let here be known that we don't give a damn about what you have to say from this point on [LAUGH]. All right, last step. Going back into task.
[00:29:31] So now, we turned out the value, what type is P? It's a CancellablePromise. And so in here, when we're deciding whether or not to go to the next step or not, we can just simply say, if, if you're not cancelled. Go to the next step. If you are cancelled, we don't care.
[00:29:56] The iterator will never get another thing out of this iterator. Or this, actually this function just runs to completion and you're done. So how this affects? The way this works.
>> Mike North: You note that we don't see that subsequent flashing coming in anymore, it's only that last result that will ever have a chance at updating the current state on this component.
[00:30:24] Everything else, we would describe this by saying we are black holing this promise. We say black hole because it's like the signal goes in and we never hear from it again, right? We can't truly cancel the fetch. We can't say it like to the browser, I no longer care about this data.
[00:30:41] But what we can do is say, the handler that typically would do its job and continue the sequence of events, ultimately resulting in like, sets date being called, that updating what's on the screen. At the nearest possible convenience when you pop up between promises, just don't go any farther.
[00:31:02] It's almost like we went to a yield and then we just say drop it, you're done, just drop it. So that's pretty cool, that's like enhanced async await. You could do the same thing with regular async and await. You would just have to check in between promises that you await and just early return from an async function in the event that you're canceled, right?
[00:31:26] But with generator functions we can implement it in a generic way that anything can consume now, like this cancellable thing that's built into the task function. Anyone who uses this, who passes in the generator function. That is setup to behave like async and await. Will be able to take advantage of the cancelability feature with the way we've implemented this.