Modularizing Game State0:03
We should have done this in the previous episode, but let's quickly do this. I want to essentially take the state management stuff and put it into a module so that we would have a clear gameState and then we would have a getGameState and then finally we would have a setGameState. The idea being that we would have this inside of modules so that, you know, for right now it's in localStorage, but what if we wanted to use, you know, a web server
that, you know, for right now it's in local storage, but what if we wanted to use, you know, a web server to manage our state or if we wanted to dive into IndexedDB, which don't want to right now, uh, if we do that then you know, we could just have these modules and swap them in and out and everything would still work essentially the same. That's what I want to strive for. So we're gonna start though with a module that would be, you know, we'll just call it browserStorage.js.
Creating browserStorage Module0:47
So we're gonna start though with a module that would be, you know, we'll just call it browserStorage.js. Now of course we don't have this file, so let's create it browserStorage.js and all we are going to do is essentially lift the functionality that we have inside of the Game class and put it inside of this browserStorage so that we would export a function called clearGameState. Then we would export a function called
that we would export a function called clearGameState. Then we would export a function called getGameState. And then finally we would export the function called saveGameState. But this would be a state object, which we would then, you know, write into localStorage as adjacent structure. So let's look at this code here. So the first time that we do this is right here, hasSavedGame.
So the first time that we do this is right here, hasSavedGame. So here we simply get that from localStorage, we will call getGameState, and then inside of our getGameState we will return where we get that from localStorage. So that's great. Now I know that we do that here pretty soon, right here where we load the game state. So we will just call getGameState once again.
where we load the game state. So we will just call getGameState once again. And then I think the next thing that we do is clear the state right here, removeItem. So we're gonna lift that code out called clearGameState and then we will paste that inside of our module. And then finally, for saveGameState, uh, what do we want? We want just this right there so that we can call saveGameState, we'll pass in our state object.
Making Storage Async2:22
that we can call saveGameState, we'll pass in our state object and then our storageModules would be responsible for getting the state object into whatever format is needed for the data storage. So yeah, great idea, but you know, here's the thing. We need to start thinking in terms of asynchronous JavaScript because localStorage is incredibly fast. It's all in memory.
because local storage is incredibly fast. It's all in memory. So anytime we get or save data, it's very fast. But if we decide to use, you know, a web server for managing our game states, well that's not instantaneous. In fact, we have to make a request, we have to wait for that request to come back with a response. So that's asynchronous JavaScript. And you know, JavaScript is single threaded. So we're gonna have to essentially change our module here so
And you know, JavaScript is single threaded. So we're gonna have to essentially change our module here so that we use it as an asynchronous module. The reason is because we want to swap these modules in and out. So they need to not necessarily work the same, but they need to definitely have the same API, which means that we essentially need to wrap our interaction with localStorage with asynchronous JavaScript. And that's really easy to do.
with local storage with asynchronous JavaScript. And that's really easy to do because local storage is what we would call friendly. It's very fast to begin with. So it's really easy to wrap that stuff with asynchronous JavaScript. And in JavaScript we use what's called a promise to do asynchronous things. So you can think of it as a placeholder for a value that you will eventually get.
So you can think of it as a placeholder for a value that you will eventually get. Another way of thinking about it is going to a restaurant and placing an order. And then what do you do? You wait and then you wait, and then you wait, and then you wait, and then you wait. And if you live where I live, you wait even longer. And then finally you get the food that you were promised. So that's essentially what this is. The Promise is how we do asynchronous JavaScript.
So that's essentially what this is. The promise is how we do asynchronous JavaScript or how we do asynchronous things in JavaScript. So we create a new promise and then we say, okay, we are going to do something, which in this case we are going to get the gameState. So whenever we create a new promise, we pass in a function that is going to initialize everything. We call this the executor. So this is going to kick off all of the async stuff,
We call this the executor. So this is going to kick off all of the async stuff, but in this particular case, you know, local storage, no need for it. But again, we are wrapping it with asynchronous so that future modules, which will be asynchronous will, will mean that we don't have to do anything else. Just oh, alright. You know what I mean? So, uh, we want to get our gameState, but you know, let's do this,
So, uh, we want to get our game state, but you know, let's do this, let's do this all in one go. We're gonna call getItem off localStorage. We want to parse the data that we get from localStorage. And this is perfectly fine to do because if the key in localStorage doesn't exist, then getItem returns null and null is gonna get passed to JSON.parse, which is going to result in null anyway.
and null is gonna get passed to json_decode, which is going to result in null anyway. So this is perfectly safe to do, unless if the data inside of local storage with this key is not valid, json, which in our case it's gonna be fine unless somebody gets in there and manually fiddles with it, which that's their problem. So we are going to get that state, but then we don't want to just return state.
So we are going to get that state, but then we don't want to just return state because a promise has essentially three states. I'm gonna use that term a lot apparently. So there are three states to a promise. The first is pending. You know, you're, you're waiting for something to happen and then when whatever happens happens, we want to resolve the promise. If everything was okay
to resolve the promise. If everything was okay and we have the data that we were expecting, we will resolve the promise with the value. That's, we are promising. But if something goes wrong, we need to reject the promise, in which case we're gonna wrap this into try catch. And we haven't really talked about try and catch and error handling or anything like that, but in JavaScript it's very simple.
and error handling or anything like that, but in JavaScript it's very simple. You can throw anything, you can catch anything there. There's no rules to it. If it's a value, if it's an object, you can throw it and catch it. It's, it's fun. So we are going to try to get data from our localStorage and if everything is fine, we will resolve with that state. Now that state could be no,
and if everything is fine, we will resolve with that state. Now that state could be null, but we are still resolving with whatever we were promising, we were promising state. If the state's there, it's there. If it's not, it's null. We gave them what we promised. But if there is an error, we are going to catch that error and we are going to reject the promise saying that, hey, guess what? There's an error and that's it.
Refactoring to Promises7:25
that, hey, guess what? There's an error and that's it. Except that we don't want to just create a new promise. We want to return this promise, which means that it's going to change the way that we use this getGameState. So we use getGameState in a couple of places. Um, the one biggie is right here, loadSavedGame. So this is gonna change because this is all procedural and procedural code, at least for right now,
So this is gonna change because this is all procedural and procedural code, at least for right now, isn't gonna work for us with asynchronous processes. So what we end up doing is something like this, we call getGameState, which is a promise, and then we do something with the state that we are given. So this function that we pass to the then method is this resolve function. So we are resolving the game states, we have the states,
is this resolve function. So we are resolving the game states, we have the states, we create our game object set the secretNumber, set the history, and then we return the game. But if something goes wrong, we want to catch where it went wrong and we want to do something with the error that occurred, which in this case it makes sense to write that out to the console. And that's that. So it, it drastically changes the way
that out to the console. And that's that. So it, it drastically changes the way that we write our code. But, and you might think, well this is a horrible approach and compared to procedural JavaScript, yeah it is, but this is, this is so much better than what it used to be. I, I originally planned to go into the history of, of what we would uh, basically called callback hell. Because when it came to asynchronous JavaScript, you'd have a callback and then you would need
Because when it came to asynchronous JavaScript, you'd have a callback and then you would need to do something asynchronous, then you'd have another callback and these callbacks would be nested. So you have this nested thing of callbacks, it was callback hell, we still have callbacks here. This is a callback function that occurs whenever the promise is resolved. This is a callback function that calls
whenever the promise is resolved. This is a callback function that calls whenever the promise is rejected, but this is a lot easier to manage than what it was. Okay? So our loadSavedGame is going to call getGameState and then it's gonna do something with the state if it's resolved or it's gonna do something with the error if it's rejected. But we need to do something more, we need to return this
or it's gonna do something with the error if it's rejected. But we need to do something more, we need to return this because the really cool thing about a promise is that even whenever you resolve or you reject, it still returns a promise. It's just built in. So what we are doing here, if, if this resolves, we are creating a game setting the secretNumbers, setting the history, returning that game. And that is essentially going to resolve to the promise
setting the history, returning that game. And that is essentially going to resolve to the promise that is being returned. So what this means is inside of ui.js right here where we load the saved game, this is gonna look like this to where we call game.loadSavedGame. And then we are gonna do something with the game. Uh, we're no longer dealing with the state because remember that loadSavedGame returns. Where is it? Where is it? There it is.
because remember that loadSavedGame returns. Where is it? Where is it? There it is. It returns a Promise that resolves with the game. I'm flying over this because I think you can follow along, but two, we're going to completely get rid of all of this in the next episode with something that that is much easier to, to write and follow along with. But the reason why we were looking at promises is because it's the basis for everything that's asynchronous.
But the reason why we were looking at promises is because it's the basis for everything that's asynchronous. So there we go. Okay, so let's go back to ui. So now we have a promise that resolves as a game. So now we have a game, but we probably shouldn't call it game because we have that, uh, variable here called Game. So let's call this savedGame. So we have our savedGame to where we set the game equal to savedGame
So we have our savedGame to where we set the game equal to savedGame and then we did the UI things, we iterate over the history and voila. So it drastically changed the way that we, well not drastically, but it did change the way that we write our JavaScript. And here's one of the things about asynchronous code. Once you start doing one thing asynchronously, it's just a chain reaction.
Once you start doing one thing asynchronously, it's just a chain reaction. Everything that you know depends upon an asynchronous process is now going to be asynchronous. That's just the nature of the beast. So this is also why it's kind of important to, before you start writing code, to think about, you know, what needs to be asynchronous and what doesn't. Because if you think of asynchronous code late in the game, you're gonna end up
Because if you think of asynchronous code late in the game, you're gonna end up changing a lot of your stuff. But that's just how it's, okay. So that is loadSavedGame. However, uh, let's go back to our game now we use getGameState elsewhere, you know, right here, in which case, now let's go back to the UI here. So what do we do if the game has savedGame, we could do this a little bit differently. We're gonna leave hasSavedGame in,
we could do this a little bit differently. We're gonna leave hasSafeGame in, in the Game class, but we're gonna do this. So we're going to call loadSafeGame just right outta the box. Boom. And if we have a saved game, if we have a saved game, what do we have? If we have state, which we might not have state now. So let's do this. If we don't have state we just return, we can return null, let's return null.
So let's do this. If we don't have savedGame we just return, we can return null, let's return null That way then we can call loadSavedGame. That gives us the ability to do this. And we could check if we have a savedGame now. So savedGame could be null or it could be an actual savedGame so that if we have a savedGame, then uh, that would be too much listing. So if we don't have a savedGame, what do we do? We don't really need to do anything, do we?
So if we don't have a saved game, what do we do? We don't really need to do anything, do we? We just need to return 'cause we're fine there. But if we do have a saved game, then we want to ask if you want to continue the saved game, in which case then we will do all of this other stuff. All right? So that'll work. Then we can get rid of that. I That, I think that works a little bit better. Maybe it, it it's probably not clearer,
Promisifying Save and Clear14:44
that works a little bit better. Maybe it, it it's probably not clearer, which we can make the argument. It's not as clear. So let's not do this, but I just did it so we're gonna keep going. Okay? So that means, let's see, we need to go back to browser storage and we need to promise apply these other things, which is gonna be rather easy. So we want to return a new promise, which we are going
which is gonna be rather easy. So we want to return a new Promise, which we are going to have our executor, which will, uh, resolve or reject. And here's the really cool thing about this. We don't have actual values to resolve here and there are times when that's perfectly fine. All we need to do is just resolve. Otherwise we would catch an error and then we would reject using that error and then that would be that.
and then we would reject using that error and then that would be that. And that's perfectly fine. There are some asynchronous things that we just wanna kick off and then hey, we're done. So that's fine. We need to do essentially the same thing for our local storage. So once again, we'll have a try catch. Uh, we shouldn't ever have an error here, but we need to
Uh, we shouldn't ever have an error here, but we need to create our promise with our executor of resolve and reject. Then we need to try and catch, in which case we will resolve and then we will catch the error if it occurs and then we would reject with the error. So because these are just kind of kicking off things, we don't necessarily need to worry about them inside of game.js. At least I think if we take a look here, yeah,
Testing Async Storage16:23
of game js. At least I think if we take a look here, yeah, we we're just clearing the game state and we're returning. There's nothing else that needs to be done. Really the same thing with SaveGameState. So yeah, everything should work as it did before. So let's refresh. I don't want to load the game. Let's play a new game. I'm gonna leave the developer tools open just in case if we run into any errors, it looks like we're gonna be fine.
I'm gonna leave the developer tools open just in case if we run into any errors, it looks like we're gonna be fine. So let's, uh, guess 12 and 1 then we will refresh. Do we want to continue the saved game? Yeah, 12 and 1 are there, if we guess 13, everything is working as it did before. The only difference now is that we now have a storage module that works asynchronously.
The, the only difference now is that we now have a storage module that works asynchronously, which means that, uh, we can, you know, start thinking in terms of using other storage options like a web server. But before we do that, I want to, well I want to change everything that we just did because while promises are are nice, it's not as nice as just clear procedural JavaScript, which the async and await keywords give us.
it's not as nice as just clear procedural JavaScript, which the async and await keywords give us. And we will look at those in the next episode.
