State, Time, and Concurrency

0 0

[Music] so my name is Alex I'm talking today about time or state time and concurrency and this is a high school senior photo so as to establish some degree of humility I'm the former core team member now ember core team emeritus I work at a company called future-proof retail we have built and maintained a suite of ember apps that kind of targeted retailers grocery stores the most popular of the apps being a mobile line free checkout app where you can scan items on your phone and check out and skip the line and so I've got plenty of experience last few years doing mobile apps and dealing with with concurrency so state time and concurrency maybe let's define a few terms so state is the data in your app that changes over time async operations which might be admitted or kicked off in your app take time and usually cause state to change and concurrency is any time that you have more than one async operation happening at the same time and I would sort of argue that the user is kind of an async operation the user will get impatient and will triple click the form submit button and will click the refresh button if things don't load fast enough so any any app that has any async on its own any time or any Ajax requests is already concurrent because there's always the user and concurrent programming is pretty hard still it's pretty hard unfortunately it's pretty much all that web development programming is so amber concurrency is an add-on that I wrote maybe a little bit more than a year ago that helps you manage and deal with state time and concurrency and it's one of the more popular in Brad on may be quick show of hands who uses Ember concurrency in a production app pretty good like 50 percent so so to sort of highlight what's actually difficult I'm actually going to stick with a pretty simple use case that gets hairy pretty quickly so this is actually a use case that's lifted from my own app for the mobile checkout where on the first things that you have to do is geolocate get your latitude longitude coordinates and then forward that information to the server so that the server can tell you nearby locations that are supportive and you can check out at using is app and so we're going to start off doing this in a very vanilla typical Ember fashion we're just going to have a action called submit form it's going to make use of a geolocation service it's going to make use of a store a data store and it's going to say geolocation got get cored just return the promise when the promise fulfills we're going to take the chords coordinates that were returned from a successful hopefully geolocation request and we're going to afford that to get nearby stores and that's going to return a promise that fulfills when we have a list of stores that are nearby then we're going to take that result and we're going to set it as a property on this component so that it's exposed to the template and the template can render it so if I click this button it's going to take two seconds and then the results are going to show up there's a fluffy fillet there's a flux central as the fluxes are us there's transclusion ZAR us which is a competitor and throughout these examples we moving around in the world so the results will change so this is not really ready for production you wouldn't shift this because clicking this button gives you absolutely no feedback as to the fact that something's happening behind the scenes so let's fix that the first thing we'll do is we'll add a is submitting flag and we're going to set it to true at the beginning of the action we're going to set it to false right after we get the results back so we can use that is submitting to drive the state of the button so now it says finding and there's at least some feedback that something is happening in the app but we're still not done because if I button mash which you can't tell that I'm doing but if I tap this a bunch of times in a row I'm actually kicking off a bunch of async operation then getting coordinates multiple times and asking the server multiple times to find nearby locations so we're still not ready to actually shift this to production so really this is an issue of unwanted concurrency we've made it really easy to kick off an async operation but we've also made it really easy to kick off in a sting operation while one is already running and that's the definition of concurrency and we want to constrain that so we just add a single line of code weary this is submitting flag and we check it at the beginning of the action and if it's true that means we've already kicked off in action before and it's not done yet so we'll just return early so if I button mash this at least solves the problem of kicking off multiple operations that just make sure that only one fires off are we are we done are we good unfortunately no because we log into our era reporter we log in a bug snag or whatever we're seeing that there's a bunch of client-side errors along the line of calling set on destroy objects we get so frustrated with ember that we throw it all of our code and we rewrite and reacts and apply the same methodologies only to realize that we run into the same error with slightly different copies which says this usually means you called set state on unmounted component so this is just a highlight that this is not an ember specific thing this is something that frameworks in general that don't really give you great facing primitives are concurrency primitives it's something that you have to deal with kind of everywhere but let's press on we're going to stick with our quickly degrading ember based example and so we had a little bit of defensive programming we check if this is destroyed so because components extend ever object member object exposed whether exposes whether an object is destroyed you can just check this that is destroyed and returned early so that these potentially dangerous sets don't happen right and I don't really have any code to validate this is actually working it's honestly when when you start really going to be by adding these to your code bases you're probably not testing each one maybe you are but it's everywhere and it's just such a harrowing task so that works but then you get a feature request from Satan who says it's good but can you add a unita cancel button we had some clients that they're geolocation isn't great they'd like to cancel maybe geo like hate via some other means or prove that they're at the store and then so you continue the downward spiral and I didn't actually finish writing this piece of code it's broken if you click click find nearby stores and click it again it like weirdly undoes itself because why would I spend so much time writing shitty code just to make it work so this doesn't work but you could imagine doing something where you track a sequence number of how many operations you've done maybe you can also keep track of the last promise that you would have stashed from the result of here and then every one of these callbacks you check if it's the most recent promise and if not that means it was called again or it was canceled and then you need to it's just the worst source so I'm going to go on a limb and say that you probably bargained with a designer or somebody making a feature request that said or you basically had to water down what they're asking you to do because you didn't know how to cancel an async operation I know I have I'm assuming and everyone else is probably guilty of that that's fine you don't need to like build out every single harebrained feature that a designer asked for but it sucks when you have to say no when ember tells you no react or whatever it says this is too hard to do it really shouldn't be so this is where ember concurrency comes in and so I've rewritten the previous codes to use the so called Ember concurrency task and really the active ingredients are just this part here where you're getting coordinates and then also passing it to get the nearby list of stores but this actually hits all the use cases all the corner cases from the previous code but mash this thing a bunch of times in a row it's not going to keep on sending me progressive results and also I can cancel it and it doesn't break I don't really want to open the console and risk that in my presentation but there's no weird errors are set on destroy or anything like that it's just safe and I'll kind of go into different things that really go into it but if you've never seen ever concurrency before if it's annuity it's probably a lot of information so football I just want to point out maybe a few things that are that are interesting about this example one is that this note is submitting flag yeah the things people get excited about it ember come so there's no in submitting flag so somehow that is addressed there's no is destroyed kind of defensive programming checks there's no check to see if I'm already running because that means you know I have to prevent concurrency by returning early and this might be new to the dimer concurrency veterans in the crowd but there's no set at all in the planning code there's no setting aside ways result property or anything so this is actually going to be when I'm sort of focusing more on this than this presentation that I have before deal with it so what actually is number concurrency I show you example using it but like it is a bunch of different API is just one thing it's really just one thing it's the task primitive and 99% of ember concurrency API involves using path really taxes like usually it means you took an action something that was in actions hash and you moved it to a task and use the generator function syntax from you implement it as a path of an action so what does the task give you it gives you a better syntax for expressing async operation similar to a single weight but in a manner that gives you cancellation support cancellation first class it is also a declarative API for constraining concurrency so not only can you express these operation in a nicer fashion of cancelable but you have an api that prevents these operations from happening at the same time using any number of approaches and what I'm focusing on is more in this Congress verb presentation is it gives you a bunch of derived State so as an example of the syntax side of things I've implemented the same a sting operation twice once as an ember concurrency task and one as an async function and they both just count to three here's the task running here's the task canceling after - here's the async function variant but this button is not actually possible to implement easy in any easy simple Embry feeling manner because facing functions as defined by the JavaScript specification are not cancelable but a lovely but the fact that they can't be canceled is kind of a deal breaker because it means the only way that you can cancel is basically anyplace that there's in a way you'd have to add some logic after the await to see if you can cancel that's kind of the exact sort of defensive programming that you want to want to avoid and there no tc39 specs for pacing function cancellation there was but it was withdrawn a few months back and latest I've heard is that we should probably get used to just using generator functions for a while which ever currency is been using for a year anyway so we not to change anything from our own and if this is like sad news if you'd rather use the night facing function syntax at least you can take some solace in the fact that the way ember concurrency treats generator functions that's semantically identical to async functions the only thing you need to know to do is take any instance where you're awaiting a promise and replace it with a yield which is the generator function keyword that you use not a wait so back to all the things that tasks you it's not just syntax is also a declarative API for constraining concurrency and always when I say constrained concurrency it usually just means make sure only one of these is running at a time so yeah that's really often the hardest part it's not that tambour concurrency is empowering you to just like spin off six billion different asynchronous tasks at the same time it's really UI driven and you eyes are usually dealing with the problem of how do I make sure that things are properly cleaned up and cancelled and only one thing is happening at a time so this is where the so-called task modifier api comes in so if you just run an ember concurrency task members not member concurrency isn't going to prevent these from overlapping in any way that's that's the default there are no concurrency constraints if you want to constraint concurrency make sure that one thing is running you've got a few options there's a few ways that you can do that one is that is to use drop drop is probably the most common one and it's one of we use in the example before it means that if there's already an instance of this task running then any attempt to perform a new task gets immediately canceled and any canceled task instance says cancel before I can even start we just say it's dropped and hence that's the the word that we use for the task modifier and she was a different version that we can use I never used and queue I thought I would but no one really uses I'm a handful were people too but kind of it's a little bit too much too much magic in practice but the other one that is really common in popular I think the most popular ones are drop and restartable and there's one or two more that bring up but restartable is when the test is already running or if you try and perform it again it's going to cancel any prior instances and then that makes room for the new one to run and it runs immediately some of you may or may not know that it's a thing called task groups I don't use these but I wanted to sort of just highlight that they exist so all the previous examples show you how to constrain concurrency for a single task but what if there's different tasks different operations and you want to make sure the only one runs at a time what you can do is you can use a task group you can say that upload file as a task that is of the my group task in my group is defined as a task group that is restartable and what that means is that if any of these if you try to perform any of the tasks in one of these groups it's going to cancel any prior instances also in that group to make room for the new one to run so as I go between these cancel buttons every time I perform it it's canceling the other ones going there are use cases for this I haven't actually personally used it yet but um I definitely got started on it pretty early because people that were early adopters have use cases for this sort of thing and so this is all stuff that I presented on before but I don't really think I've talked too much about the so called derived state which I want to focus on a little bit more and by derive state I just mean there's always an opportunity and we're always going to allow the flexibility to let you track your own state by introducing another property on a component or controller or router whatever and you can just manually update the state of fat over time so you could have used is my test run you could have done is submitting from the earlier example and then put a bunch of this that set to update that value over time but instead of doing that there's actually a lot of properties that are kind of built into tasks into these task instances that you should know about because they can save you a lot of pain a lot of error-prone code so it's a very basic example of some stuff that's given to you for free is running I didn't actually show you any of the template code before I will soon for that example with the geolocation but if you have a task that's called ship widget I'm probably going to screw up on them if I say it a few times ship widgets and you perform it then is running is going to go to true the form count is going to increment one or one concurrency is always going to reflect the number the current number of running tasks so this one doesn't have any concurrency constraints if I just button mash this thing concurrency is going to go up and it's going to go down and if anybody ends up you know playing around these slides later it's kind of a fun game to see how high you can get concurrency before it goes back down somebody actually cheated and made like a little jQuery plugins or half the button million times again the things people get excited about so just with there's way more derived state than just what I showed on the previous slide but I wanted to sort of highlight a quick thing because I don't think the very many people appreciate this aspect of every kind of concurrency so much and actually I keep tabs of how people are actually using this stuff there's like public github repos that use it sometimes people will send me code snippets if it's you know something that came out even they're using a pneumatic ember concurrency but still came out a little weird so one that I've seen a handful of times it's almost as if people don't even know the API sort of exists or they just haven't sort of practiced this style of thinking but if ship widgets has a is shipping widget booming property that it's managing the state of then it's tempting to say okay I'll just set it to true at the beginning of the test and set it to false at the end but there's obviously some problems with it it's completely redundant why would you ever introduce more state that you need to track yourself and probably get wrong when you can use just something that's built in it's at the point of failure your chances are you're probably going to reorganize when these things get set to true and false and it's going to break in very subtle ways because there's all when you write code this way there's all these timing dependencies for when you set properties and when you decide to mutate some property on the component and it's actually broken it's not just error prone this is actually wrong because if you were to cancel this task while it's paused on this line of code then this line is never going to get run and you're going to like you're going to have shipping widget be set to true even though nothing's actually shipping there's another instance that I didn't write down here but I kind of just struck me this can run concurrently because there's no constraints and if you run this eight times in a row the first task to get to this line of code is going to say is shipping which at fault even though there might be seven more instances that are still running so use the right state when you can because you're probably going to get it wrong otherwise because we're dealing with concurrency concurrent programming is really hard so use what's what's given to you and there's sort of a minor problem I want to point out a lot as big as the other issues but every property that you introduce every piece of state also needs to have a name and developers like to bicker over how to name things do what you call this flag is shipping widgets or is ship widgets running maybe another developer I'd have a problem with that because that meant you'd need to capitalize the s and then it's harder to grep and so there's all these little death by a thousand paper cuts problems that you can avoid when you just use the state that is kind of built into a task and then you can just very easily just grep your program and see what are all the components that updates when ship widgets state changes so it's a minor problem but it kind of comes up a lot and it's one of these cases where it's just a little bit of convention that prevents a lot of like drift between apps between developers so if this task returns one two three you would think that there's some way that we could actually access that and you already saw an example where you did with the the geo-locating example we didn't use that set so obviously somehow we were taking the return value of that task and bringing it to the screen so would it be ship widgets value would be something out and so I want to talk about how ever concurrency is put a lot of effort a lot of but a lot of the API that design that went into it is there to respect the fourth dimension the fourth dimension being time because there's a lot of I've seen a lot of attempts to build nice conventions over promises promise proxies but they don't really they don't really they kind of those conventions cut a little too deep I guess is one way of putting it they kind of choose a happy past that plays favorites and doesn't actually make it easy to design a variety of async use case so we want to we want to honor convention over configuration but we don't want to take it so far that actually hurts our own creativity so convention over configuration is great when these shared battletoads battle-tested conventions don't limit your potential don't limit your creativity but when they do and when they play favorites when the api's make certain use cases and data flows really easy to do but others really hard and sort of make you feel bad for even trying them that's obviously not good so I'm trying to avoid that with ever concurrency and I think an example of this and it's kind of a API that I worked on a few years back but the the fact that you're very much encouraged to load models and the route model hook and it kind of sort of a subtle shame that if you ever try to do data loading and component the frame you're just kind of just slightly punishes you for wanting to do that but if you don't and you load all of your data in a model hook then you're sort of in a class of ember apps with that sort of feel very similar to all the other ones you know that when you click a link it's you might use a loading route and cost them overlay to go over there and it just doesn't go there immediately it's just you can tell you're dealing with like a 2013/2014 ember app in the same way that you know you started off using Twitter bootstrap the CSS framework and you never really fully separated and built your own thing then you it's just another app that looks like it's wood strata so this is sort of that effect applied to when convention over configuration takes sort of favorites with certain use cases so ember concurrency doesn't always provide a particular convention but it has gives you conventional language to express it doesn't choose for you how you what kind of data you want to display what is the most recent successful value from a task that completed it all this will all make sense in just a moment I promise I know I'm mixing the words up but this is a long way of saying that isn't just shift widgets dot value it's shift widgets that last successful value and last successful could actually be any number of things so let's actually dive into what that means so it's not dot value this is always going to be undefined but you could use chip widgets that last up value or you could use last successful that value so what are the you see one of these properties I'm talking about when it last was less successful so when you perform a task it creates a task instance which represents a single execution of that task which may run the completion it might fail with an exception or might be canceled it might possibly be canceled before it even has a chance to run if you're using the drop modifier and it's these things that actually expose the dot value the dot error rather than being some sort of upper level global property on that on the task object so if you recall this example if you perform this a bunch of times you're going to be creating a bunch of task instances that belong to my task and dot value and dot error exists on here and when you want to actually have access to those values because they're very useful for driving data in the template you basically have to say which one you actually want because there's multiple task instances they'll be created under a given task so in other words with concurrency there isn't just one dot value there isn't just one attempt to load the data on the screen and there isn't one attempt to hit the server yet the sort of tell ember concurrency which is the one that you actually want and when you do you have the freedom to basically define how stale or how eager your your UIs are when you start reloading you get into like reloading data so the top level tasks in this case ship widgets exposes properties to recently perform tasks instances one of them is left which is the most one that you just performed which might still be running it might eventually run to completion or have an error last successful is the last one that ran to completion return the value last erred is the one that threw an error which can be useful sometimes for displaying like error loading banners and some other things I don't expect you to really take very much away from this slide but just to make it clear these are the air these are sort of the last and last successful and last air these are tasks instances that ship widgets the tasks will maintain a reference to and every task instance has a value error is successful error so is this clown town is this have you flipped the bozo bit on what I'm saying am I really expecting to learn all this stuff we'll really I'm actually expecting you to maybe if you're interested learn two properties on a task and a few properties on task incidents and mentally construct that matrix in your head of which piece of data you actually want but right I mean it's worse than you you end up with code that sort of looks like like this so why would you ever do that so come back to this old example I didn't really show you the template code for this but just so you know have a sense of what this looks like in practice here's Irish I guess here's what the here's here's the piece of code that actually exposes data to the template we say with submit form that last successful value as a result and then we just you know loop over it and display all the stores and that's how this works and what we use last accessible here but with just a slight tweak of the property that you use to drill down to the specific task instance who whose values whose data you care about with just a slight tweak you can sort of change this UI from something that is always displaying data even if it might be stale even if the user has already expressed interest in getting a more up-to-date version of that data you can just make one change you could replace last successful with last which this is the only thing that changed down here and then when you've already got resolved then when you press the button again the results clear and you basically chosen an API that is as fresh as possible that never wants to display stale data and this might seem like a minor thing but it's just it's one of these cases that we're usually the api's play favorites they they promise proxies you have to swap out the property which meant you lost access to the old attempt to fetch data and it's just one of these like slight missteps that makes it really hard to for you to move between different styles of facing data loading with any sort of like alacrity so it's really nice at all that you have to do is just change one property which value do what do you want the one that last completed or do you girlie want the newest value even if it doesn't exist yet makes it really really easy it's so really this just boils down to the fact that ember concurrency pays a lot of respect to the concept of essential state versus accidental state and these are terms that are sort of originated from a white paper form I think 2006 called out of the tar pit and basically the paper that talks about the common cases of complexity in that come out of software and they sort of say that the root of all evil and the cause of a lot of complexity is so-called accidental state it's a state that you don't actually need to reconstruct you know the page that you're viewing or the operations that you're doing it's just state that sort of got duplicated even though you've sort of expressed it elsewhere and could just read arrive it and just very simple example is if for some reason you had an object in your code that was a user's array that had two elements in it but you also had a user count thing to you know you should wonder why this is there why did I need to actually make this a piece of data that I may or may not have to update over time when it's just I know we just say users dot length why would you ever do that so that's a very simple example of accidental state and the antidote of accidental state is to use derive state which is a way of just recomputing the values that you need based on this bare minimum essential state and kin in an ember your you probably have some experience with using derive state if you use computed properties so these you should be familiar concepts the only man to know about ember concurrency is that it pays due respect to derive state over time it keeps it just enough amount of information and make it really easy to do sort of comparisons has the data changed is the data stale all these different things that are usually pretty hard in without something like ember concurrency usually requires a lot of manual state tracking which is really easy to get wrong in an async concurrence environment so in general this is just the idea that you should prefer to cleared of api's over imperative api's because property mutations particularly when interleave with a bunch of async operations happening at the same time quickly leads to just a comment or unreasonable mess of code that is just going to be error-prone and it's really hard to change and evolve over time so as much as there's never going to be a silver bullet and Emer concurrency definitely has its warts and its problems it's definitely worth taking some time to sort of think about these acing problems using derived State and I'm hoping I can get more people interested in because I I want em per concurrency to give you even more than there already is so it takes practice to think in this way but I think it's worth the effort and I think it's also kind of exciting because members concurrency is kind of the only thing like it I haven't seen anything like it there's a few there's actually a few copycat coming out there's a react concurrency I don't think I saw an actual angular concurrency but I saw a article that is reimplemented that task function for put for supplying a generator a function that you know runs async logic and so I think people recognize that this is missing from other worlds and that we're kind of privileged in ember to have this so hopefully you might get excited about this and talks to some of those stuff to me but uh so in conclusion number is really just three things it's a powerful syntax for expressing a sink and a way that's cancelable the powerful declarative API for constraining concurrency and it's a ton of derived State that you can build really expressive UI's with without going down the path of mutating a bunch of properties and managing state over time that's it thank [Applause] [Music]