Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The promise of async functions

Lindsey
February 03, 2016

The promise of async functions

A journey to the center of the toolchain

Lindsey

February 03, 2016
Tweet

More Decks by Lindsey

Other Decks in Programming

Transcript

  1. The promise of async functions Journey to the center of

    the toolchain • I normally talk about Ember but tonight I decided to venture out of my comfort zone • tonight we’re going to journey to the center of our toolchain in search of the promise of async functions
  2. Where we’ve been: callbacks foo(function (value1) { bar(value1, function(value2) {

    baz(value2, function(value3) { quux(value3, function(value4) { quuux(value4, function(value5) { // Do something with value 5 }); }); }); }); }); • if you’ve done any programming in javascript you’re likely familiar with the concept of asynchronous programming • if you’ve been doing said programming for a while, you’ll’ve lived through the rise and fall of a couple of different paradigms covering how we handle async programming • this will probably look familiar • this kind of nested callback structure has fallen out of favor though - and you can begin to understand why even looking at this made up code block - and over the past few years many of us have migrated to a different pattern, based on promises
  3. Where we are: promises foo().then(function (value1) { return bar(value1); }).then(function

    (value2) { return baz(value2); }).then(function (value3) { return quux(value3); }).then(function (value4) { return quuux(value4); }).then(function (value5) { // Do something with value 5 }); • promises entered our mainstream consciousness in 2011 with jQuery 1.5, but were predated by mochikit and dojo a year or so earlier • the term itself was coined in, get this, 1976 • that’s older than me. but this is javascript so only the last 5 years counts right? • still, quite a lot of noise here. there are lots of function keywords. there’s overhead in knowing the function signature of then() • though these days at least there’s a standard we can follow - one that even jQuery will follow in its next major release • and a standard means we can use promises as a building block for other abstractions • using that building block, might there be an even better way to write code that needs to handle asynchronous behaviour?
  4. Where we might be: async functions async function example() {

    let value1 = await foo(); let value2 = await bar(value1); let value3 = await baz(value2); let value4 = await quux(value3); let value5 = await quuux(value4); // Do something with value 5 } • async functions are part of the ecmascript 7 specification • an async function is a function that has inbuilt knowledge of how promises work, allowing it to pause itself while promises are resolved • they’re expressive and terse - the parentheses and periods of the promise style have been removed, in favor of a couple of new keywords • two new keywords, async, here, and await, here • the async keyword says to us, “this function returns a promise” • the await keyword says to us, “I am going to pause here until this promise resolves” • any old promise! including ones produced by jQuery! • now, I could show you a bunch of syntax - and we could for sure spend more time looking into the details of things like error handling - but that’s honestly better served by a blog post and a dev console and you all poking around on your own • lets dive into something more interesting: how do we actually ship this! • because let’s be honest, consistent browser support for ES7 features is a looong way off
  5. Transpil-what-now • if you start to ask about shipping new

    standards to old browsers, you’ll likely see this logo pop up • babel is the mindshare leader for transpilation - though it calls itself a JavaScript compiler these days • transpilation is a term that you’ve probably heard more and more over the past couple of years • transpilation is defined as the process of converting one set of source code to another set of source code • in our case, it’s converting ecmascript7 syntax, to ecmascript5 syntax • ecmascript 5 could be considered the baseline standard that you need for your JS to work everywhere, being supported by older versions of IE • usually the transpilation process creates a very clean mapping between new, es6 and es7 code that you’ve written and the old, es5 code that gets delivered to a browser
  6. Transpilation Most code in ES6... let exclaim = (string) =>

    `${string}!`; … translates easily to ES5: var exclaim = function exclaim(string) { return string + '!'; }; • you can pretty easily go back and forth between these two blocks of code and understand how they relate to each other • there are 3 bits of ES6 syntax here: “let”, a fat arrow function, and a template string • we can see how that converts into “var”, a normal function, and string concatenation • I find it interesting to see how new standards desugar • “desugar” is another way of saying “convert back to es5” • I like seeing what actually gets delivered to our users
  7. Transpilation This code in ES7… async function example() { let

    value1 = await foo(); let value2 = await bar(value1); let value3 = await baz(value2); let value4 = await quux(value3); let value5 = await quuux(value4); // Do something with value 5 return value5; } … converted into ES5... • so lets see what happens when we transpile our ES7 async function back into something that we can run in all of our browsers
  8. An async function, transpiled function example() { var value1, value2,

    value3, value4, value5; return regeneratorRuntime.async(function example$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: context$1$0.next = 2; return regeneratorRuntime.awrap(foo()); case 2: value1 = context$1$0.sent; context$1$0.next = 5; return regeneratorRuntime.awrap(bar(value1)); case 5: value2 = context$1$0.sent; context$1$0.next = 8; return regeneratorRuntime.awrap(baz(value2)); case 8: value3 = context$1$0.sent; context$1$0.next = 11; return regeneratorRuntime.awrap(quux(value3)); case 11: value4 = context$1$0.sent; context$1$0.next = 14; return regeneratorRuntime.awrap(quuux(value4)); case 14: value5 = context$1$0.sent; return context$1$0.abrupt('return', value5); case 16: case 'end': return context$1$0.stop(); } }, null, this); } • AHHH that is certainly not an immediately understandable conversion • first up: don’t worry about understanding this code right now. it’s for the machines, not us humans • having said that, there are similarities: the function name is the same, we can see the inner functions - foo, bar, etc - there’s this “return” bit here that might return the value from our original source code • so what’s going on, why is this so much more complicated than our previous example?
  9. Transpilation is for syntax • by definition transpilation is only

    for source code, for syntax. it’s not necessarily behaviour! and that’s what we just saw • while we can transpile syntax, there is no 1:1 mapping of async function behaviour to ES5 code • therefore, we need to go through an intermediate step: • to get the behaviour of async functions we actually need to turn to ES6 • specifically, a lesser known - or at least lesser used - part of the specification called generators
  10. Generators function* vegetableOut() { yield 'carrot'; yield 'potato'; } …

    which returns a generator object, which when invoked... let g = vegetableOut(); g.next(); // -> {"value":"carrot","done":false} g.next(); // -> {"value":"potato","done":false} g.next(); // -> {"done":true} • this is a generator function • a generator function is a function thats execution can be paused, and resumed, all the while maintaining its own inner state • like an async function, there are two little bits of syntax that tell us that this is a generator • the asterisk after function says to us: I will return a generator object when you invoke me • the yield in the function body says: when you ask me to, I will give you this value, then pause my execution • the concept of “pausing” execution is kind of weird, but it might make more sense when we look at how the generator object is used • the generator object is our way of controlling the generators execution, and it’s also our window into the state of the generator • you can call the “next” method on the object and it’ll give us the next value that has been “yield”ed • within the generator function, it will jump ahead to just before the next “yield” statement • so in our example, first, carrot, then, potato • after we’ve yielded everything we can the generator object will also tell us that we’re done, that there is nothing else to be yielded
  11. Generators, cont. function* fruitInVegetableOut() { var fruit = yield 'carrot';

    var secondFruit = yield 'potato'; console.log(fruit, secondFruit); } var g = fruitInVegetableOut(); g.next(); // -> {"value":"carrot","done":false} g.next('apple'); // -> {"value":"potato","done":false} g.next('orange'); // -> {"done":true} // "apple', "orange" • but that’s not all! remember how I said that a generator maintains its inner state? • using the generator object you can also input data back into a running generator, altering that state • as with our previous example, the first time we call next “carrot” comes out • but the next time we call “next”, we call it with an argument: “apple” • this gets passed into the generator function, and stored there as the variable “fruit” • likewise, the next time we call “next” we get “potato” out, and pass “orange” in • and at the end of the generator - when there’s nothing left to yield and the function can finish its execution - we can see that both fruits have been set • using this behaviour, and knowing that async functions work exclusively with promises, we can start to piece together how generators allow us to implement the behaviour of async functions today
  12. Generators ♥ Promises Given a generator function and function that

    returns a promise... function* myGenerator() { var value1 = yield myPromise(); // ... etc... } … get the generator object… var g = myGenerator(); … and combine it with promises… g.next().value.then(function (value1) { g.next(value1); }); • so, what's worse than presenting complex syntax in a presentation? complex behaviour • I’m going to quickly run through the high level of how promises and generators combine, but thankfully this is something you shouldn’t ever have to write on your own, as we’ll discuss shortly • so: • given a generator that has within it a yield to a function that returns a promise • we invoke the generator function, returning a generator object • then we call next() on the generator object to move its execution to the yield, which invokes the function that returns our first promise • the output of that next() call is an object with a “value” property - which we know now is a promise • we chain on another then() call to that promise - one of great things about standardised promises is we know we can always add a new callback using then() • when the promise that we’re working with resolves, it will call that callback, with a return value • inside the callback, we then call next() again, passing into the generator the value of the promise
  13. • this restarts the generator, with that return value of

    the first promise now available inside • if you duplicate that logic for each yield in the generator, you can repeat until the generator object is "done" • if you wrap all of that in an outer promise, then you can use it to return the value of the final inner promise • which is the behaviour of an async function! • you could say that that final outer promise is literally the promise of an async function • ok, great, now we have the behaviour of async functions licked, let’s start using them in the browser!
  14. Here we go again... • not quite • see, we

    have the same problem with generators that we did with async functions, in that they’re just not available in our browsers • generators do have one big advantage over async functions in this regard though: we can implement the behaviour of generators using the syntax we already have in ES5 • and don’t worry, we don’t have to write all of the code to do it each time
  15. Polyfills are for behaviour • because of polyfils • a

    polyfill is a library that “fills in gaps” in browser support • basically, they implement what will one day be behaviour that is native to the browser, in the JavaScript syntax of today • you might have used polyfils to implement features like forEach and map on arrays, back when ES5 was less prevalent • in the same way, you can include a polyfill for ecmascript 6 features on the string, array, and object prototypes • now, I just took you through a really basic overview of how promises and generators can work together to create the behaviour of async functions • there was a lot of stuff I left out though: edge cases, and anything to do with error handling • we don’t have to worry about that though, because someone has already gone to the trouble of creating a polyfill for generators that fully implements them
  16. regenerator • when you transpile async functions with babel, it

    does so in a way that works with this generator polyfill, regenerator • it’s by facebook, and is extremely standards compliant. it implements error handling, for one • I don’t have time to go through how it works in this talk, but suffice to say there’s some pretty amazing code rearrangement that turns a generator function into a big state machine • that’s what that big blob of code I showed you earlier was - a state machine • each time you call next() or throw an error it moves through a list of possible states • this slide deck will have a link to a blog post that explains it - perhaps someone can do a followup talk on it • regenerator also takes care of wrapping our transpiled generator in a promise, so that the transpiled code can adhere to the async function specification • so, that’s it. we’ve transpiled our code and added a polyfill and now we can use async functions
  17. Resolve the promise of tomorrow's JavaScript, today. • I started

    by showing you the syntax of async functions, but, to be perfectly honest, you’re probably better off learning syntax from a blog post at your own pace. • what I really want you to take away from this talk is: understand the new features coming down the standards pipeline, but also understand how they’ll be delivered to users today • once you learn about a new feature, see how it fits into your toolchain - does it transpile neatly? will you need to include a new polyfill? • trying to parse that big blob of transpiled code can be overwhelming at first, but as we’ve shown you can break down each part of how it was created and unravel them one by one. • we’ve seen how transpiling our ecmascript 7 syntax into ecmascript 5, and utilising a polyfill to add in ecmascript 6 behavior, has allowed us to fulfill the promise of async functions in the browser • knowing what happens to your code when you run it through your tools gives you the confidence to resolve the promise of tomorrow's javascript, today
  18. Resources - http://babeljs.io/ & http://facebook.github.io/regenerator/ - The toolchain - http://www.sitepoint.com/simplifying-asynchronous-coding-es7-async-

    functions/ - Simplifying Asynchronous Coding with ES7 Async Functions - http://alexperry.io/javascript/2015/09/17/es6-generators-and- asynchronous-javascript.html - ES6 Generators and asynchronous javascript - http://gu.illau.me/posts/polyfilling-generators/ - Polyfilling generators