6 ms·
ES7 async functions – a step in the wrong direction (2015)
- cromwellian 11y agoYes, there are some edge cases, but the reality is, I've seen code festooned with Promises, and code using async/await, and I find the async/await code far more readable. You could make a similar argument that Promises are insufficient, and you should really be using Observables, because there are lots of situations where the generality is even better. On the other hand, there's no universal hammer, and no reason why you can't mix all approaches, choosing each where it works best.
- skrebbel 11y agoThis argument basically boils down to "I think elegant abstractions are more important than readable code". This is a pretty valid position to hold, but I strongly doubt the majority of the JS community agrees. A similar thing is how when you return a Promise from a 'then' callback, the resulting promise does not resolve to an inner promise but just to the value that was inside the inner promise. This completely breaks the monadic properties of Promise, but given that JS has no nice support for monads, it makes code simpler and easier. IMO, but Promises/A+ spec designers made the right call here. Purists will disagree, but I bet the majority of the JS community hasn't even given this a thought (yet uses the feature all the time). I feel the same about async/await. Also, anecdotally, at my company we're heavy heavy users of async/await ever since Babel added support for it, and we've yet to run into any of the problems the author refers to.
- omgtehlion 11y agoIn my current project we were using async/await even before they appeared in Babel (via homegrown transpiler). Been happy so far.
- tracker1 11y agoI have to agree... just because someone may have a case where they need multi-stage transactions or cancellable workers doesn't mean that Promises and async/await aren't useful as they stand. I wrote a ton of code last year to migrate data from one system to another... without async/await, Promises and node-style streams, it would have been really nasty, deeply nested code... as it was, the flows were fairly functional, really elegant, and worked extremely well. I will say that Bluebird is useful beyond what the Promises spec offers, and have gone back and forth between using it, replacing the native Promise where available and just using whatever promise shim or native is available. I didn't like promises earlier on, as without some of the extras that async functions offer, I didn't consider them much better than callbacks. Now, I'm sold.
- spion 11y agoNot sure what you mean by multi-stage transactions? It applies for ordinary transactions - its just a way to avoid passing a transaction parameter everywhere but still guaranteeing that the entire batch of queries executed within a single transaction. I don't think the problems I'm describing here are all that uncommon in server-side code. Continuation local storage (the equivalent of thread local storage) in particular seems to be an often requested feature by many users in node.
- maxthegeek1 11y agoThe issue is that we could have landed on syntax that was both more readable and allowed for elegant abstractions.
- braythwayt 11y ago> "I think elegant abstractions are more important than readable code” Typically, that is not the argument. Typically, the argument is that the "elegant abstraction" is just as readable, just in a different way. And further, the typical argument for elegant abstractions is that the “readable code” option is usually most readable for a specific, narrow problem to solve, and that it breaks down when you try to do more complicated things. So if you buy the argument in favour of elegant abstractions, you are trying to make “simple things simple, and complicated things possible.” Whereas the argument in favour of special keywords and other special-purpose constructions is that they also make simple things simple, but do little to make complicated things possible. The argument in favour of languages having these “readability” constructs is actually that they foster standardization, which makes them readable by virtue of the fact that everyone settles on doing them the same way. Which means in fact that they aren’t more readable in an isolated, objective sense. They’re actually more familiar. That has value in its own right, of course. But I don’t think the author--or anyone else who isn’t thrilled by some of the TC-39 choices--is arguing that there is an elegance vs. readability dichotomy. In actual fact, the argument is between elegance and familiarity by fiat.
- spriggan3 11y ago> Lets drop async/await and write our own interpreters. Let's "codify" patterns instead of having everybody roll his own implementation thus make codebases more compatible. All criticisms made in the article are 100% valid, however the former principle trumps every other one. I don't want having to import yet another micro-module just to write something that should be trivial to do in plain javascript. As for try/catch, some languages support pattern matching, why not Javascript?
- carussell 11y agoSome form of catch guards have been implemented in Firefox for over a decade, so if you were running SpiderNode, you could use them right now. try { (17).toString(42); } catch (ex if ex instanceof RangeError) { console.log("caught"); } catch (ex) { console.log("handling something else"); } The if's clause has standard standard conditional semantics, so you can use any expression there; you aren't limited to selecting on the exception's prototype: try { throw new Error("whatever"); } catch (ex if ex.message == "whatever") { console.log("caught"); }
- tracker1 11y agoWell, it's entirely possible to submit that as a stage-0, then it could get in babel and be used sooner than later.
- amasad 11y agoInteresting write up, examples, and ideas, but the conclusion is a bit silly: >Lets drop async/await and write our own interpreters. Guess what kind of "interpreter" most people would need? The async/await kind. It doesn't solve everything and it does introduce more awkwardness[1] to the language but it's categorically a productivity and readability boost. Now to address the individual points raised: 1. Try/Catch The argument here is against a core language feature and has nothing to do with async/await. Sure, guarded catch statements would be nice, but I contend that if you're relying on multiple levels of exception handling then you're using exceptions for flow control. 2. Higher order functions JavaScript is a multi-paradigm language and as mentioned earlier that leads to awkwardness. You can only pull the language in so many directions before it becomes inconsistent. Some features will be geared towards functional paradigms and other will be imperative by nature. For example instead of `forEeach` you can use `for of` which allows you to yield inside the loop. In that sense async/await, for-of, and try/catch are all imperative control flow constructs. On the other hand callbacks, promises, and higher order functions are functional constructs. Mixing them together is not easy (nor should it be if you ask me). 3. "generality" In this case you might not want to use such a high-level construct like async/await. Just like when you have to do caching or memoization you tend to drop down to do promises again. However, you can push this down into a library that exposes a promise and then continue using async/await in the rest of your application. In this specific case you want a monad-like abstraction, something that carries certain context across a sequence of computations. The generator implementation might be an overkill but the syntax looks nice. [1]: This is a great write up on how adding different types of functions to the language makes it awkward to say the least: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
- true_religion 11y ago> The argument here is against a core language feature and has nothing to do with async/await. Sure, guarded catch statements would be nice, but I contend that if you're relying on multiple levels of exception handling then you're using exceptions for flow control. I don't see how this is a problem. Try/Catch are by definition flow control keywords. Also, when you get an exception you have to differentiate between how you recover from it or package it into something that can be logged or used by a higher level caller. That said, I don't disagree with any of your other statements.
- nikki93 11y agoWait, so are you saying that async functions are a step in the wrong direction, or are you actually saying that you like them but would want to add features / do it better?
- mbrock 11y agoI like when languages are small and simple. The overlap between generators and async/await seems a little strange. So my feeling is that the "wrong direction" is towards language complexity.
- jonesb6 11y agoCool I guess. But people will still find a way to write bad ES7. Will it be harder to do? Maybe. Hard to tell until it gets implemented by all major browsers, at which point i'll have given up programming for professional hover board racing sponsered by ubersoft (microsoft got aquired after the second robot uprising). Edit: Also after reading the article it strikes me that ES6 and ES7 features are going to create a huge gap in developer knowledge between those who fully understand ES7/8 and those who only understand ES5. Will we one day hear "oh beginners should start by learning ES5 and then do ES7?". Doesn't that sound a lot like c++?? Do we want that for javascript?
- coroutines 11y agoI primarily work in Coffeescript. A lot of the things I love from Coffeescript got picked up in ES6/ES7, so it wasn't a big deal for me to transition. :-) (sad that Coffeescript will decline when it has more to offer..) I had to work on an ES5 project recently and I was almost in tears because I couldn't use a computed property. (someObject[someValue]) I wound up writing a switch to assign the correct index, and I felt very sad about my life. Someday people might not learn about how we kept things private in closures. They'll have a `private` keyword to make their transpiler do it for them. It has become less important to directly know how inheritance works, so people might be happy having a `class` syntax. There are a few times I've wanted to make an object fallback on another without making an official "class", so maybe this will be secret juju someday. I tend to view language development like overfilled buckets. When JS becomes as complicated and unwieldly as C++, we'll spill into the next bucket in an effort to create another simple language.
- pavlov 11y agoI had to work on an ES5 project recently and I was almost in tears because I couldn't use a computed property. (someObject[someValue]) I wound up writing a switch to assign the correct index, and I felt very sad about my life. Just checking -- is this a parody of a JavaScript hipster, or do you seriously get depressed by writing a switch statement?
- 11y ago
- true_religion 11y ago> Since promises are a userland library, restrictions like the above do not apply. We can write our own promise implementation that demands the use of a predicate filter Does such a library exist already? It seems like it'd be a very useful thing.
- esailija 11y agohttp://bluebirdjs.com/docs/api/catch.html http://bluebirdjs.com/docs/api/catch.html
- kenOfYugen 11y ago> Generators are JavaScript's programmable semicolons. Lets not take away that power by taking away the programmability. Lets drop async/await and write our own interpreters. One of "our own interpreters" (i.e not standard in JS), that I am very fond of, is js-csp [1]. It's a replica of Clojure(Script)'s core.async, or Go's channels/goroutines. It uses ES6 generator functions underneath. James Long has written an excellent post about it [2]. I find it to be the less taxing on the mind solution for coordinating complex asynchronous processes in JS, especially when paired with transducers [3]. Furthermore, powerful Go/Clojure patterns can easily be re-used in JS. In conjunction with CoffeeScript, the code becomes elegant and straightforward since there is no 'function*' ugliness required. That's the least complex solution I have found to like the most. Performance and memory usage suffer a bit, but it's very straightforward to optimize if/when needed. [1] https://github.com/ubolonton/js-csp https://github.com/ubolonton/js-csp [2] http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-JavaScript http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-... [3] http://jlongster.com/Transducers.js--A-JavaScript-Library-for-Transformation-of-Data http://jlongster.com/Transducers.js--A-JavaScript-Library-fo...
- spion 11y agoWhile we're at nicking stuff from Go using generators, our latest effort is adding defer in bluebird :) https://github.com/petkaantonov/bluebird/issues/1014 https://github.com/petkaantonov/bluebird/issues/1014 Its almost done [1], we just want to also support auto-disposable objects so that you don't even need to clean them up with defer. [1]: https://github.com/petkaantonov/bluebird/commit/f944db753ff25e43561fb6c65a6664417b9892cd#diff-3129285757f0c44eca9973432b3bb15aR704 https://github.com/petkaantonov/bluebird/commit/f944db753ff2...
- DonHopkins 11y ago"Programmable semicolons" is a cool sounding term, although I don't quite understand what it really means in relation to JavaScript. Are programmable semicolons optional, and can you program automatically inserted semicolons? It brings to mind my favorite C++ extension: Generalized Overloading for C++2000, that lets you override specific types of white space including space, tab, newline, and even the absence of white space! [1] FORTH has programmable semicolons! : ; POSTPONE EXIT REVEAL POSTPONE [ ; IMMEDIATE There's an interesting comp.lang.forth discussion [2] where somebody asks a great question about defining semicolon in Forth, that's promptly answered by the great Elizabeth D. Rather herself, of FORTH Inc. [3] [4]! [1] http://www.stroustrup.com/whitespace98.pdf http://www.stroustrup.com/whitespace98.pdf [2] https://groups.google.com/forum/#!topic/comp.lang.forth/79XRi5PtK6M https://groups.google.com/forum/#!topic/comp.lang.forth/79XR... [3] https://en.wikipedia.org/wiki/Elizabeth_Rather https://en.wikipedia.org/wiki/Elizabeth_Rather [4] https://archive.org/details/Programm1984 https://archive.org/details/Programm1984
- scotty79 11y agoIf language has primitives for some concepts then people tend to use those because they have special syntax that makes using them more convenient. For example if language has special syntax for numbers people tend to use number as type of account (or string for contact person). Then when software evolves and new information needs to be kept for account and new operations performed the shorthand syntax stands in the way. You can't easily change the type of account from number to complex object without refactoring all of the places in your code where account was used as number. (I'm ignoring languages with overridable operators to make the point). People tend to obsessively stick to built in stuff (anti-pattern Primitive Obsession) because swapping for what they need now is excessive burden. What I would very much like to see in languages development is that whenever the syntax gets introduced programmer should be able to use it on more complex things then the syntax was primarily designed for. If you introduce syntax for operators don't make them work just for few built in types. If you introduce async/await don't make them just work on promises (especially not just built ins) but anything that is shaped like a promise. If you introduce generators make sure that they programmer can implement persistable version of them with the same syntax.
- esailija 11y agoAsync/await doesn't only work on promises, it works on any object that has a method named `then`. What's problematic is that you have no control or extension points in the runner so you cannot implement something like Go's defer statement. With generators (which can be made work like async/await, only you say yield instead of await) you can implement the defer statement and more.
- scotty79 11y agoGood that it works on any object with then. Shame that it's not more flexible. What I said was more general argument about what each new syntax should support at minimum. What you are saying is like having operator overload, but not ability to define new custom operators, or not being able to define 3 or more arguments operators or default arguments for operators, or quoted arguments for operators. Or regretting that C# foreach works on every Enumerable but takes every item one after another (preventing from implementing collection that would have parallel behaviour with foreach) It's good if you can do a lot with new syntax but that's nice to have and case by case. What I was talking about I consider general and pretty much mandatory. Lots of people feel that if you look at the piece of code you should be immediately be able to tell what it's doing without any context. It's intuitively attractive goal but I think that most measures that achieve that also result in putting obstacles in software usual evolution making people do very contorted things when they need to add yet another feature with limited time.
- sotojuan 11y agoIt seems like every time something nice and new comes to JS it's immediately called bad.
- EvanPlaice 11y agoJust wait until the :: function bind operator is added. I expect the Haskell community to s* enough bricks to build another Empire State building.
- mbell 11y ago> And voila, we've just implemented a query optimizer. It will fetch all issue owners with a single query. If we add an SQL parser into the mix, it should be possible to rewrite real SQL queries. Maybe my sarcasm detector is off but is this really being presented as a reasonable solution to the problem discussed in the article?
- spion 11y agoSorry, I'm not being quite clear there - I'm just trying to illustrate that a programmable execution engine could have many uses (one of them being the ability to automatically optimize the n+1 queries problem).
- BinaryIdiot 11y agoHonestly I would prefer this to promise. Yeah promises are nice and all but I keep getting myself into projects where promises are nested even worse than callback soup which ultimately ends up just confusing me because it's not simple and straight forward anymore. Though I still don't understand why the ECMAScript standards body keeps updating almost only syntax. They've done so little to the standard library that it's maddening. Honestly I would take zero syntax changes / improvements if it meant getting first class, JavaScript standard utilities for, say, dealing with HTTP / HTTPS.
- MrOrelliOReilly 11y agoYou mean like https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API ?
- BinaryIdiot 11y agoNo because that is only for the web. It's not part of the ECMAScript standard. There are packages to make it work in node and hacks to make it work on more browsers. But no standard.
- edgyswingset 11y ago> Cannot use higher order functions This code: async function renderChapters(urls) { urls.map(getJSON).forEach(j => (await j).html)); } is assuming that the call to await j is the await which corresponds to the top-level async. That's not how these things work! That call to await j is not in the same scope! I don't buy arguments where the misuses of a feature not working means the feature is somehow bad. Yes, it can seem confusing, but that's the nature of having more powerful constructs. When you mix asynchronous code and lambda expressions, you're going to have to pay attention to scope.
- Akkuma 11y agoUsing a library like Bluebird couldn't you simply write and get the exact behavior intended? async function renderChapters(urls) { Promise.map(urls, getJSON).each(j => j.html)); } or async function renderChapters(urls) { var chapters = await Promise.map(urls, getJSON); chapters.forEach(j => j.html); }
- carapace 11y agoTo me this sort of reads like someone discovering lisp (but in JS.)
- jsprogrammer 11y agoWhat you're looking for is Async Generators [0]. They were in traceur master not too long ago, but I'm not sure if they have been pulled out ([0] indicates that the proposal was pulled and replaced with the lesser Observable). [0] https://github.com/jhusain/asyncgenerator https://github.com/jhusain/asyncgenerator