Transcription
Hello, GS Nation. Welcome to the future features of JavaScript. In this talk, we will be going through different proposals from stage 0 to stage 3, understand why they exist, what they're trying to solve, and hopefully they get implemented in all of the JavaScript environments. I'm Hemant. I'm an engineering manager here at PayPal, a TC39 delegate, a Google developer expert for web and payments domain. You can hit me at Newman on Twitter or find me on html.com. The ECMA International has a lot of technical communities, out of which TC39 happens to be the ECMAScript community, which is responsible for the specification that JavaScript implements. The TC39 process includes different stages, starting from stage 0 to stage 4, where stage 4 is finished and implemented in different environments, and stage 1 is more like a strawman with just basic idea of what the proposal is. In this talk, we will go from stage 0 to stage 3 and see different proposals. If we look into the current state of how the proposals are, on stage 0, we have 18 proposals. On stage 1, we have 91 proposals, and stage 2, we have 22, and stage 3, we have 17, and stage 4, we have 59 proposals. Let's dive into and surprise ourselves with some of these proposals. Starting with stage 3, say we have an array of objects and you want to find a particular value from the end of the array. What would you do? You would probably do a reverse on that array and do a find. In this case, we are trying to find the values which are not divisible by 2. In this case, we got value as 3, but we had to reverse the array and then do a find. What if we had a convenient method which says, array.findFromLast, array.findLast, here is the condition, give me the element. What if you were to find the last index? You would again do a reverse and do a find.index, and take the array.length and subtract minus 1 from it and subtract that value from whatever we found if the result is matched to find the index. In this case, the value was 2. Suppose the condition is not met, where we are looking for number 42. The value should have been minus 1, but it is 4. What if we had a method which said, array.findLastIndex, and when the condition is met, it would find the index if it's present, and if not, it would give minus 1. Wouldn't it be convenient? Here on the left is a comment from one of the issues in GitHub for this proposal, where the idea was to discuss and come up with names. As you can see, some of the names that were popped up in the issue was findRight, findIndexRight, findLast, findIndexLast, findEnd, findIndexEnd, and so on. And finally, we have findLast and findLastIndex as the proposal today. On the right side, we can see an implementation from the Chromium source, which says, fast array, findLast. If you were to open up spec, you could do a one-on-one mapping to each of those lines in this code to understand how the specification is implemented. For example, on the comment where it says 4, that's 0.4. In the spec, we say, let k be length minus 1, and that's where we have the exact implementation here. And that is findLast and findLastIndex. Then we have import assertion. This kind of proposal gives a way to assert the type that we're importing. For example, in this case, we are saying import JSON from foo.json, assert type JSON. We are saying, OK, this is of type JSON. And if you were to use dynamic imports, you would say import foo.json and say assert type JSON when you are doing a dynamic import. And if, say, you have a JavaScript which is exporting a value, and you want to export that value from your file, you could say export value from foo.js, which is exporting that value. And then you are asserting type to be JavaScript. And finally, you could use type to be WebAssembly. And this can also be used in the script tag. And you might be wondering, why should I use an assert type? Right? Why do I need this inline thing? Why not just import JSON from foo.json? Well, it happens just relying on the MIME type to figure out whether it's JSON or not causes a security bug. There are a lot of concerns around security on just importing a random file. And then you have to make sure that you are asserting this is of type JSON. And if it is something else apart from JSON, it would throw and say, hey, I don't know, because I'm trying to assert this to be a JSON file, and it's not, thereby keeping the code more secure and giving us an easy way to express ourselves inline on what type is this in terms of asserting that this is what we are really importing. Similarly, we have JSON modules, which is very much similar to what we saw previously, you can import JSON as modules and having this assertion type. But you might be wondering, why two different proposals? Is it very much similar to what we just saw previously? Well, if you dig into the notes from the meeting, you can notice here that there was a discussion and the decision that was made which said, one of the decisions was that to decouple JSON modules, so that it makes into stage two, and then progresses to different states, right? This is a common thing that happens during the phase of a proposal, it might get modified, it get changed, can branch into two different proposals, it can also get discarded, rejected to a lot of reasons. So here we saw that JSON module was part of the import assertion, but become an independent proposal in itself and so that it can accelerate quickly towards the finish. Definitely don't miss this talk from Yulia, which is part of JS Nation this time, and she's talking about how JS modules work from a browser's perspective. She's a team in the 39 delegate, of course, and as an implementer in Firefox, this talk is must watch if you want to know more about how JSON modules work. The other proposal we have is change array by copy. As the name says, it's you're changing an array by copy. Let's see some of the examples here. Say you have a sequence, an array which 1, 2, 3, and then you say 2 reversed, of course, you get 3 to 1. But interestingly, the sequence remains the same, we are not mutating the sequence. And that's where it's super useful when you're not mutating the array, but still getting the reverse of the array. Similarly, you have sort, 2 sorted, where you are going to sort, say in this case, you went 8 arrays, and you could say out of order, 2 sorted, you get the sorted array, but the original array stays as is. And we have the bit method where if you want to make a change to the array, in this case, the correction needed is the array where we have 1, 1, and 3, and we want to have 42 in the index 1 rather than 1. So we say correction needed with 142, we get 142, 3, but the original array remains as is, that's 1, 1, 3. Say you want to splice, you could also call the 2 spliced. Here we have 2 on, so we get 5410 from an array which was 543210. So we splice that, but the input original array remains same and is not changed. This is a very convenient method. We have array groupby and array groupby to map. Let's say we have an inventory with veggies and fruits, in this example, with different quantities. Then you could just say inventory.groupby type and you get an object which has the keys as fruit and veggies and the values are array of the respective values from the inventory. Then we have inventory.groupby to map, which does very much similar to what groupby does, but it returns as a map, so we can do all the map operations on the return value. Unlike inventory.groupby gives you an object, here you get a map. So this is for convenience, so you could do all the map operations. And this has many use cases and this is super useful. Up next, we have stage 2. Say we have a generator which yields multiples of 2. And if you were to convert that into an array, so what would you do? Today you would take an array and use an for of await syntax and use it on the generator and keep pushing it to the array and then you would probably get an array. So basically converting that into an array. So what if we had an easy method? Array from async, you give an async generator, you await on it and what you get back is an array. Wouldn't that make life easier with just this one liner, which is super convenient? And yes, if you are thinking about promises, well, we could do the same thing as we did in the previous example. If your generator is yielding promises and then you were to convert, you wanted to convert that into an array of promises, then you could just say async from array, generate promises, await on it and you would get an array. This makes life very easy if you're trying to work on generators and async objects and trying to convert them into an array. So it's async from array. It's very much similar to an array.from where you could pass an object which is like an array and get it converted to an array if you give it a length. And similarly, it's an extension of that which says array from async rather than just array from and pass it to an object. The next proposal, what we have is throw expressions. Say we have a function which says save and it is receiving a parameter. And what if the user doesn't pass in a parameter or what if the user passing a parameter that you don't want them to pass? You would probably check the type and do all the required assertions and then throw if it's not matching your expectation. What if this can be done as an expression in line? In this case, you say file name equals throw new error type error argument required. This would throw automatically if that argument is not passed into the save function. You could also have it within the arrow function bodies. In this case, you have an extract syntax free AST with lint which has a with method as one of its keys. If someone is trying to use the with method, then you could just throw saying that, hey, avoid using the with method, which statement. Then you can also use it in logical operators. Say you have a getter and setter within the class and for setter, you would say if the value is not passed, then you could throw saying that invalid value if it's not the value that you're looking for. And you could also use it in conditional expressions. And if all your conditional expression exhaust, you can just throw a new error saying that unsupported encoding. In this case, you have the different encoders from use the duty of eight to so on. That is throw expressions. Records and TPUs are one of the most important and interesting proposals in the recent past, which kind of brings in immutability to JavaScript. So we all know when we do strict equalities of two objects with the same key value pairs, they are false even if the same holds for others with the same values, but we do a strict equality that's false. But if you have this syntax where you have a hash for what looks like an object, you tell and you're in hash for what looks like an array. They both equate themselves to be true. And that is according to Puel where you could say type of hash, which looks like an object, which is immutable is a record and type of hash, which looks like an array, which is immutable again is a TPU. So if you have an object which you want to convert into a record, you could just call it with a record constructor and you would get the record back. Or if you have an array which you want to convert into TPU, you could set TPU from, pass it an array and you get TPU back. And definitely do watch Robin Richard's talk on records and TPU immutable data structures in JavaScript, where he goes in detail. Also TC39 delegate, and this is a part of the JS Nation talk series too. Next we have the pipeline operator. Say we have this example, which is Funzall logging chalk.dim object.keys of environment variables, mapping or environment variables and taking the key value pairs and joining it. Finally joining the entire arcs to kind of create an output. Isn't this bit confusing? Like let's see how the brain probably would try to evaluate this. It would say object.keys from left side, then we have map on the right side and we have join on the right side and the template literal on both sides, the tilde characters. And then we have chalk.dim and then we have Funzall log. It's a lot of nesting and convoluted code. What if we were to make it more easier, right? Let's see this example, where it's just object.keys of environment variables, map the environment variables, return me this string and then join. And then you have this strange looking syntax, which is pipe. And then it says dollar dollar percentage where percentage basically is the value that evaluated in the previous step. Then we say chalk.dim percentage. Again that percentage is replaced by the value that was evaluated in previous step. And finally we console log percentage. That's the value that's evaluated in the previous step. Thereby making it easy to read and understand also to change different methods. And that's pipeline operator. There are two competing proposals for pipeline operators, Hack and Fsharp pipelines. But there was another third proposal, which was kind of a smart mix of these two proposals, but that has been withdrawn. This is what we have currently today for the pipeline operator. Map.implace is another interesting proposal where say you have a map and you want to check whether the key exists within the map or not before setting it, which is a common use case. You would say if not map has key, then map.setKeyValue. Then you do map.get and do things. What if it could be more simpler and you say map.implace key insert value do the thing, right? It would basically check if that key is there or not and then insert. And probably you would have also done this. Map.get get the old value. If the old value is not there, then set the value else set the updated value. What if we could avoid these two lookups and make it more simpler? You could do map.implace and put a key there and you have update and insert methods within implace and you could operate based on your use case. And map.implace makes it more simpler in avoiding those extra lookups. Iterator helper is a large proposal. And let me try to fit it in this slide and see the different things that iterator helper provides. Right, so say you have a function which is a generator function which kind of generates all the natural numbers. And of course, if you do result.next, you keep getting those numbers. What if you were to do a map on this iterator? Yes, this proposal talks about it. You could do a map. You want to do a filter? Yes, we could do a filter. And here's an example. You have an async iterator from URLs. You get an async iterator, then do a map on it, then do an await fetch and then get the response JSON. Finally, do a two array. So you have an array of all of those responses as JSONs. And then if you want to do a take, if you want to be a bit lazy and just take only three of them from those huge series that this generator is providing, you could do that. If you want to drop three, you could do that. If you want to have an, if you do dot values, you get that iterator and you want to do as index space, then you get an index pair that that would be like a zero X and one Y and two Z and so on. And then you could also do a for each, every, some, find, all of this on values. That's Iterate Helper where it makes life easier to iterate over all of these different methods and having async iterators. Now we have stage one, say you have an array of ABCD and you want to do array.slice one three. What do we get? Is it BC or BCD? Say we have an array of ABCD again, and we do array.slice. What do we get? ABC or D, right? Well, this can be easier with slice notation, which is bit intuitive where you say array three colon. That means give me D. And if I say one colon three, that's one to three, that's BC, give me BC. If I have minus two, start from the end, give me AB. I have minus 10, which is out of bounds, give me an empty array. So if you were from the Python land, you would have probably used this a lot. And many of the languages do provide this facility and it would be super nice to see this in JavaScript. Type annotation is a big proposal and created a lot of noise in the public domain and too many people are interested because of how people are used to TypeScript these days in terms of using type. Here's what the proposal basically is about. If you're used to JS doc, you'd probably have used something like this where you said, hey, here's my string, string, string function right from the reading of the proposal. Where we have P1 as a parameter, the string and P2 is a string and so on. And this is how you would probably put it in your JS doc. And with this proposal, it looks very much similar to the TypeScript syntax here where you would say P1 is string, P2 is optional, where it's a string and P2, P3 is optional and string and P4 is defaulting to test. And this function returns a string, right? So Type1 annotation gives us this kind of a syntax where you could add the annotations to your methods and it's an evolving proposal and it's very interesting to read about the details. Next we have array unique by. As the name says, say you have an array of, in this case we have 1, 2, 3, 3, 2, 1 and we do an unique by, we get 1, 2, 3. But it gets interesting if the array has objects, right? And you want to do unique by UID, then you could do that. Or you want to do an unique by which takes an ID, UID, you want to do those pairs and probably get unique by, you could get that too. So unique by is a very convenient method to kind of get unique values from an array which has duplicates. Number range and bigInt range is a proposal that kind of gives this range method on bigInt and numbers. So in the first example, we have a for-off loop where the bigInt range starts from 0 into 43N which kind of locks 0 to 42N numbers. If you are referring to the iterator helper that we previously saw, where you can do a number range from 0 to infinity, take say a thousand of them and filter the numbers that are not divisible by 3 and then do a 2 array, you would get those numbers. Or say you have a generator which generates just even numbers from 0 to infinity and you want to, you just want numbers from, like get all the odd numbers from say 1 to 99. You could do number range 1 to 100 of 2, then you would get those. So that's the range method on number and bigInt range. Function one is another proposal that's interesting. Say you have a function that you want to execute only once for various reasons. And then say in this example, you say function once and you say f.once as f1s and f1s of 3 would print 3 and return 6. That's the console log that's there in the function. But f, again, if you call the same function f1s with 3, it doesn't print anything but return 6. f1s of 2 also return 6, doesn't print anything. So it's just execute once and if you keep executing it again, you just get back the same value that was evaluated when it was invoked for the very first time. There are some interesting discussions in the issue for function ones. Say you have a function which is a recursive code and you do a g.once and then you call g once, what should happen? Or say you have a function g of x which does stuff which takes a lot of time and has async operations in it. And then you say async g once and you get promise one and you do async g once again, you get a promise two. What should happen? So these are some of the interesting discussions that happens in proposal and it's a good practice to go and read the issues and try to dig and understand what's happening and we could also contribute with your opinions. String.cook is another interesting proposal where say you have an Unicode sequence here for say u61, you do string.raw, you get the same thing back. You do a string.raw and then pass in an object which has a key raw and which has an array of value of that Unicode sequence, you would get an A. So if you do a console log, you probably get A for this Unicode sequence. What if we had string.cook instead of saying string.raw with passing in an object with raw as its key and the value being an array with that value that you want to kind of convert it into a string? Well, it's complex, right? You might sometimes miss that there is this property with string.cook that it behaves in a way if you pass an object with raw as its key. Rather, if you had string.cook and just passed slash u61, you would get A. So this proposal makes it simple and make your code less error prone while you're operating with string.raw. ToExpression is one such proposal which makes things simpler in evaluating and assigning values to say written values or to variables. In this example, it's a JSX example where you have a doExpression which says if logged in, then return a logged out button. If else, you return a login button. This is a pretty common construct in JSX. You would probably use a conditional operator or try to return this component. Here, we're just putting a doExpression that evaluates on whatever is evaluated to true is the value that it returns. Let's see another simple example here. Say we have let x on a doExpression and we say let temp is the function call. And then we say temp into temp plus one. So temp into temp plus one is whatever that evaluates gets assigned to the variable x. Similarly, if you have x do with else, else, and else, whatever condition matches and whether f or g or h method is called, whatever the return value of those method gets assigned to x. Thereby making it more simpler in terms of assigning the values to variables when there is a nested construct and there are too many things to return and evaluate. On the similar lane, we have async doExpression. Say we have async operations within the do block. So we could say async do and say fetch a thing and return a JSON response and fetch another thing, get a JSON response, put it in promise.all and evaluate and you get back the result. Pattern matching is another interesting proposal where say you are fetching some JSON service in the example and you get a result and you do a match on matchable. Then you say when say my status is 404, I could say JSON not found or I could just pick up the status and do a check myself and say if status is greater than 400, then throw a request error and so on. And partial application, if you are into a functional programming, then you would really know what a partial application is. Say you have a function which takes n number of arguments and you're passing partial number of arguments to it rather than passing everything. And later on, you would pass in the other argument as your function progresses. And in this example, you could do a partial application from the left or partial application from the right or you could do a partial application of any argument. And that's where the partial application comes into action. And the question mark is the placeholder which says, hey, this argument will be passed on later on. Finally, we are on stage zero where we have this bind syntax. Say we have object followed by the double colon, which is the bind syntax and function, which basically is equal to object.function.bind object. Say if you were to use console log as a variable a few years back in the history, you would probably do a console.log bind console so that the object is bound to the right context. Similarly, if you have object followed by this bind syntax function value, which is equal into function.call object value. And if you were to use a this bind syntax object.function value, that is equal to saying that object.function.call object value. So let's see a quick example to make it much simpler. So you have an iterator library, which gives you map, take while, and for each. And we have a get player function, which kind of gives you all of the players and you are mapping through those players. Even though it's not mappable, you do a map.call and then get the characters. Then you do a take while.call and get the strengths which are greater than 100. And then finally do a for each.call to loop over them and console log. It would have been more simpler to use the this bind syntax. You would say get player, this bind syntax, map it with x.characters, bind it with take while again, get the strength and do a for each. So everything is bound properly, the execution context, and you get the value that you're expecting. The final one, what we have is string characters. Say we have a string with a dash underscore dash ABC dash underscore dash and the characters we have is a dash and underscore. We could say string dot trim characters that would find all of those characters to trim it off. So we just have ABC and then we could do a string dot trim characters. You could do a trim start that would just do, you know, trim the characters at the beginning. So the dash underscore dash is gone and what we have is ABC underscore ABC dash underscore and if you do it from the end, the dash underscore dash from the end is trimmed off. So that is string trimming of characters. That is all we have folks and hope you liked all of the proposals and I'm super excited about these proposals and hope all of them make it to different JavaScript environments and we reap the benefit of it. Thank you.