A place to cache linked articles (think custom and personal wayback machine)
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

4 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. title: Why I’m Not Waiting On ‘await’ (part 1)
  2. url: https://blog.getify.com/not-awaiting-1/
  3. hash_url: bfebf03c42d9609fef758bfc9a305f81
  4. <div>
  5. <p>Two-part series on the highs and lows of the coming <code>async..await</code> in JS:</p>
  6. <ul>
  7. <li><a href="https://blog.getify.com/not-awaiting-1">Part 1</a>: what <code>async..await</code> is and why it’s awesome</li>
  8. <li><a href="https://blog.getify.com/not-awaiting-2">Part 2</a>: why <code>async..await</code> falls short compared to generators+promises</li>
  9. </ul>
  10. </div>
  11. <hr/>
  12. <hr/>
  13. <p>The hype is strong for the <code>async..await</code> syntax that’s likely getting official spec approval for ES2017, but has already <a href="https://chromium.googlesource.com/v8/v8.git/+/d08c0304c5779223d6c468373af4815ec3ccdb84">landed in v8</a> and <a href="https://blogs.windows.com/msedgedev/2015/09/30/asynchronous-code-gets-easier-with-es2016-async-function-support-in-chakra-and-microsoft-edge/">Microsoft Edge</a>, at least. And in some ways, <code>async await</code> is very promising and the hype is well deserved.</p>
  14. <p>In fact, it seems even before we got Promises in ES6, most developers were already desperately pining for <code>async function</code> so they could escape some the vagaries of the Promise API and promise chain hell. There’s no doubt that a lot of code will be improved as we move callbacks to promises, and then from promises to promise-driven <code>async function</code>s.</p>
  15. <p>But is <code>async..await</code> our long-awaited white knight? Is it really the only <em>future</em> of async flow control programming in JavaScript?</p>
  16. <p>I like the pattern that <code>async..await</code> signals. Really, I do. It’s a pattern I’ve been advocating for several years now, since even before ES6 landed. But the substance of <code>async..await</code> leaves me hanging in a few places.</p>
  17. <h3 id="why-the-hype">Why The Hype?</h3>
  18. <p>Put simply, an <code>async function</code> promotes inside it a synchronous-looking style of flow control (value resolution, error handling).</p>
  19. <p>Each asynchronous step — usually the retrieval of a value or waiting for an action to finish — is modeled with a Promise. But instead of having to <code>.then(..)</code> the promise and provide a function to receive this value/completion, the <code>await</code> keyword is able to locally block while waiting to unwrap the promise.</p>
  20. <p>It’s better to illustrate with code. Here’s three versions of the same (fictional) code, one with callbacks, one with promise chains, and the final with <code>async..await</code>.</p>
  21. <p>First, callbacks:</p>
  22. <pre class="code">
  23. function getOrderDetails(orderID,cb) {
  24. db.find( "orders", orderID, function(err,order){
  25. if (!err) {
  26. db.find(
  27. "customers",
  28. order.customerID,
  29. function(err,customer){
  30. if (!err) {
  31. order.customer = customer;
  32. cb( null, order );
  33. }
  34. else cb(err);
  35. }
  36. );
  37. }
  38. else cb(err);
  39. });
  40. }
  41. getOrderDetails( 1234, function(err,order){
  42. if (!err) {
  43. displayOrder(order);
  44. }
  45. else showError(err);
  46. });
  47. </pre>
  48. <hr/>
  49. <hr/>
  50. <div id="inline-vs-declaration">
  51. <p><strong>Update:</strong> Some readers expressed disdain at the usage of nested function expressions here, since it’s putting the worst light on the visual aesthetic of callbacks. That’s not actually <em>my</em> argument in this post — that they’re ugly — but I can see why it would be distracting to my overall points for those who prefer more readable callback style code.</p>
  52. <p><a href="https://gist.github.com/getify/28c586cff83516cbd2dca573eb4cb194">Here’s a comparison</a> between this version and using separate function declarations for each. I think the readability is definitely better. But, that’s just an aside; <a href="https://github.com/getify/You-Dont-Know-JS/blob/master/async%20&amp;%20performance/ch2.md#trust-issues">the virtues of promises are about far more than just nesting/indentation/readability</a>. That discussion is for <a href="https://blog.getify.com/promises-part-2/">another post</a>.</p>
  53. </div>
  54. <hr/>
  55. <hr/>
  56. <p>No sense in belaboring explanation of this code too much. That’s not really the point of the post. But do notice that error handling is intertwined with and cluttering up the flow control of data, and that we have to nest functions to express their temporal dependence (one has to start after the other finishes, etc).</p>
  57. <p>OK, now the promises version:</p>
  58. <pre class="code">
  59. function getOrderDetails(orderID) {
  60. return db.find( "orders", orderID )
  61. .then(function(order){
  62. return db.find( "customers", order.customerID )
  63. .then(function(customer){
  64. order.customer = customer;
  65. return order;
  66. });
  67. });
  68. }
  69. getOrderDetails( 1234 )
  70. .then( displayOrder, showError );
  71. </pre>
  72. <p>Phew! That’s a lot better.</p>
  73. <p>Crucially, we see here that the error handling part of the flow control becomes implicit (and thus hidden), which declutters the code. If any error happens at any depth of this promise chain, it’ll propagate out and get handled by <code>showError(..)</code> — we don’t need to manually push it out.</p>
  74. <p>We still have to use functions to unwrap our promises, though. And indeed, we also still have some unfortunate nesting going on. The reason the <code>then(function(customer)..</code> has to be nested instead of chained off the top-level promise chain in <code>getOrderDetails(..)</code> is because we need lexical scope access to <code>order</code> so we can combine <code>order</code> and <code>customer</code> into a single value to pass along.</p>
  75. <p>OK, so what’s <code>async..await</code> going to do for us?</p>
  76. <pre class="code">
  77. async function getOrderDetails(orderID) {
  78. var order = await db.find( "orders", orderID );
  79. var customer = await db.find( "customers", order.customerID );
  80. order.customer = customer;
  81. return order;
  82. }
  83. getOrderDetails( 1234 )
  84. .then( displayOrder, showError );
  85. </pre>
  86. <p>Wow. Just wow.</p>
  87. <p>The <code>await</code> keyword locally blocks to <em>wait</em> on the promise that <code>db.find(..)</code> gives us, meaning once it resolves, we’ll get the order we can then synchronously assign to <code>order</code>; same for <code>customer</code>.</p>
  88. <p>If either of those results in a rejected promise (generally, an error), a synchronous exception is thrown at that point in the flow of <code>getOrderDetails(..)</code>, and the rest of that function body won’t run.</p>
  89. <p>And, since <code>getOrderDetails(..)</code> is an <code>async function</code>, it makes its promise. Any uncaught exception inside it becomes a rejection of <em>that</em> promise, so we route either to <code>displayOrder(..)</code> or <code>showError(..)</code> implicitly.</p>
  90. <p>If the <code>getOrderDetails( 1234 )</code> call was itself inside an <code>async function</code>, it would use the same <code>await</code> unwrapping trick along with the ol’ synchronous, familiar <code>try..catch</code>:</p>
  91. <pre class="code">
  92. async function main() {
  93. try {
  94. showOrder( await getOrderDetails( 1234 ) );
  95. }
  96. catch (err) {
  97. showError( err );
  98. }
  99. }
  100. </pre>
  101. <p>The <code>await</code> unwrapping magic is nice to facilitate asynchronous function composition.</p>
  102. <p>Is the implicit error routing of the former snippet better than the explicit form here in the latter? I prefer the explicit handling at the end of the path. I like that promises implicitly pass along rejections, but at the end of the line when it’s time to handle the error, I think that explicit <code>try..catch</code> is much clearer.</p>
  103. <h3 id="have-we-seen-this-before">Have We Seen This Before?</h3>
  104. <p>Are you sold on <code>async..await</code> yet? I think they’re pretty cool.</p>
  105. <p>But as I’m a true blooded hipster, I liked this pattern before the rest of you thought it was cool.</p>
  106. <p><em>Way back in ES6</em>, we could actually do this same pattern, just with slightly different vocabulary: <strong>Generators</strong> and <strong><code>yield</code></strong> (with promises):</p>
  107. <pre class="code">
  108. function *getOrderDetails(orderID) {
  109. var order = yield db.find( "orders", orderID );
  110. var customer = yield db.find( "customers", order.customerID );
  111. order.customer = customer;
  112. return order;
  113. }
  114. run( getOrderDetails( 1234 ) )
  115. .then( displayOrder, showError );
  116. </pre>
  117. <p>What’s different from <code>async..await</code>? Well, we see that ugly <code>*</code> there instead of the prettier <code>async</code> word. We also see <code>yield</code> instead of <code>await</code>. And lastly, we see usage of <code>run(..)</code> — where the heck does that come from?</p>
  118. <p>I’ve <a href="https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&amp;%20beyond/ch4.md#generators--promises">written</a> extensively <a href="https://davidwalsh.name/async-generators">before</a> about using generators and promises to facilitate this synchronous-async pattern before, and about why that’s so important. Read those if you’re not sure what is going on in that above snippet.</p>
  119. <p>The short version is that generators don’t come out-of-the-box with knowing how to receive a <code>yield</code>ed promise and how to resume when it finishes. So we conjure a <code>run(..)</code> function that is smart enough. I won’t show the implementation of it here, but it’s listed in the first of those two links, and it’s only about 25 LoC even with comments — not too difficult, but not a detail to get tripped up on here.</p>
  120. <p>The point is, <code>run(..)</code> is a known-quantity, and can be (and is!) easily provided by various async-helper libraries so you don’t have to worry about it, but just use it.</p>
  121. <p>Of course, I think you <em>should</em> eventually learn how it works, and that’s what those other readings will teach you. But for now, let’s just use it as-is.</p>
  122. <h3 id="a-rose-by-any-other-name">A Rose By Any Other Name…</h3>
  123. <p>Is an <code>async function</code> just nicer syntactic sugar for a generator with a <code>run(..)</code> utility driving it?</p>
  124. <p><strong>No.</strong> But I can see why most people find that natural/comforting to assume.</p>
  125. <p>Let’s start with <code>yield</code> and <code>await</code>. I think it’s fair to claim that <code>yield</code> is imperative while <code>await</code> is declarative, but that may take a bit more explanation to convince you.</p>
  126. <p><code>yield</code> is inexorably connected to the process of locally pausing to <em>yield</em> control to some external facility, and, if appropriate, eventually resume the generator. The sending of a value, and the receiving of a value back, are entirely optional; the return of control is all that <em>really</em> matters. We’re imperatively saying “yield control to somewhere else and wait until its given back”. <code>yield</code> is primarily a control-transfer mechanism.</p>
  127. <p>With <code>await</code>, we’ve covered up all the mechanics and semantics of any external control involved; the engine itself does that <code>run(..)</code> work for us. As far as we’re concerned, semantically, control never leaves our local function, we just sort of sit and a<em>wait</em> for processing to finish so we can move on to the next line.</p>
  128. <p>Also, the usage of <code>await</code> seems to be a bit more focused on value semantics than <code>yield</code>. “What is this line waiting on?”” It’s waiting on a value. Would you use <code>await</code> just for pausing and not capturing any return value? I don’t think that’d be as natural, because the whole notion of transfer of control is hidden, conceptually and syntactically.</p>
  129. <p>Taking promises out of the equation for a moment, you could use <code>yield;</code> in a generator to just simply transfer control elsewhere for awhile, and neither have a value you’re sending out nor have a value you’re expecting back.</p>
  130. <p>But you can’t <code>await;</code>.</p>
  131. <p>The <code>await</code> keyword has to always explicitly be awaiting on a promise. Even if you did <code>await undefined</code> or <code>await 3</code>, those immediate values are implicitly wrapped in an already-resolved promise, which is then <code>await</code>ed.</p>
  132. <p>In other words, <code>await</code> is declarative in that it’s saying “await on this value until it’s ready”. The <em>how</em> of that is entirely hidden so we aren’t even thinking about it. Even if under the covers, the browser did exactly the same steps with <code>await</code> that would be done with a <code>yield</code>, the mental models and the syntax surface area are declarative for <code>await</code> and imperative for <code>yield</code>.</p>
  133. <p>We all like declarative forms, though. So that seems like a clear win, right?</p>
  134. <p>The downside is when a declarative form removes important functional characteristics.</p>
  135. <h3 id="just-use-both">Just Use Both?</h3>
  136. <p>In <a href="https://blog.getify.com/not-awaiting-2">part two</a> of this blog post, I’m going to explain my specific reservations of <code>async function</code>s.</p>
  137. <p>But to conclude this post, let me address the most obvious question floating in the air right now: if <code>async function</code> is nice for syntactic sugar, and generators+promises can do more powerful things when needed, why not just use both?</p>
  138. <p>We wouldn’t need a <a href="https://blog.getify.com/not-awaiting-2">part two</a> blog post if I felt the world was that easy!</p>
  139. <p>The concern I have with mixing <code>async function</code> and generator is that the composition of the two is more awkward. It’s easy from a generator to <code>yield foo()</code> if <code>foo()</code> is an <code>async function</code>, but it’s weirder from an <code>async function</code> to <code>await run( foo() )</code> if <code>foo()</code> is a generator.</p>
  140. <p>By “weirder”, I mean that it pushes that extra <code>run(..)</code> tax out to every call-site of <code>foo()</code>, but if most of your code is based around <code>async function</code>, you’re pretty likely to forget that you need to put <code>run(..)</code> in there.</p>
  141. <p>Unfortunately, you won’t necessarily detect this error like you’d expect. Since <code>await</code> automatically promise-wraps any non-promise values, it’ll just promise-wrap the iterator that comes back from your generator invocation, never having run any of the code in the generator at all! And since the value is a non-promise, the wrapped promise will immediately resolve, meaning <code>await</code> unwraps it right away.</p>
  142. <p>The take-away is that mixing the two in the same code base is going to be error-prone. I’m not just predicting… I’ve tried it, and made these mistakes. Many times.</p>
  143. <p>Check out <a href="https://blog.getify.com/not-awaiting-2">part two</a> where I explain the reasons I plan to stick with generators+promises instead of jumping on the <code>async function</code> bandwagon.</p>