Broader adoption of JS on the server will only begin after ES6 generators land in Node. Due to the complexity of async code, writing JS on the server is not easy for the average team.
As of now, you could use Promises but it is still kind of messy without generators. Here is a simple example:
We use FaceBook's regenerator project to compile ES6 features down to Node's currently supported Javascript features. If you don't want to use the --harmony flag, you should really try it out. https://github.com/facebook/regenerator
Whilst thanks to lack of choice JavaScript is so important on the browser side, I would hope the Node infatuation would eventually pass where you do have a choice. Async calls in Node are done via explicit passing of closures. Callbacks, Promises etc all look bad compared to simple sequential instructions like:
The above is in Go, and it is just as fast if not more. Whilst all requests are on their own goroutines, the runtime will schedule them using the same efficient async io that does not need to be made visible as in JavaScript.
I was hoping it shows that it really depends on your style (procedural or functional). Thanks to generators and arrow functions, ES6 lets you write code using either.
When you're writing in functional style, you'd use arrows, which work nicely both for small lambdas and for writing combinators that can be partially applied. When you're writing in a more procedural style, generators are your "do syntax" equivalent(-ish):
This isn't a question of "my style" -- zsombor's notation is objectively better. It achieves the same results with fewer tokens and less intellectual overhead.
The difference between "functional" and "procedural" style here is nothing more than monadic bind operator vs do-notation, but what you have above or in the initial response is nothing like that.
Also I'm not sure "arrows" -- http://www.haskell.org/haskellwiki/Arrow -- means what you think it means in the context of functional programming? It's not sugar for `function(){}`...
Whoops, yes I mean "arrow function syntax", not arrows.
As for style, using combinators is usually considered to be specific to functional programming, right?
IMO the interesting part is how the terse arrow function syntax in ES6 puts combinators on the same level of expressiveness as everything else. Contrast to ES5 you have to use the awkward `return function() {}` syntax, and you have to do something similar in go - `return func (param type) type { ... }`
Both styles are now almost equally terse and you can pick either depending whether you feel its more appropriate in the given situation.
Those aren't go routines, there's a yield sitting right there. And there's yield/return in your second example, and there's the equivalent in the promises example.
This isn't a terseness issue; it's an error issue. Favoring yield/return and equivalents over sequential code in CSP is exactly the same as favoring goto over while, or goto over exceptions. Necessary sometimes? Yes. Something to be celebrated and preferred? No.
The trouble is, of course, that in Javascript runtimes the "yield" or equivalent is how scheduling is achieved, because there is no capacity for preemption.
Thus, Node.js returns us to the bad old days of Windows 3.1 and System 6, where one faulty bit of code grinds the entire system to a halt.
Edit: Go goroutines of course also have a cooperative scheduler currently, but at least the result of having it isn't baked into the idioms and language constructs.
True. Though I suppose that writing an async generator runner that utilizes multiple workers (e.g. via webworkers in browser, via isolates in node - if they ever happen) is also possibly doable at some time in the future. Those generators wont have any access to the current scope, but that might be better[1]
Still, scheduling would continue to be cooperative via yield (just split amongst multiple threads). Go is clearly much further ahead in that regard :D
note: I'm curious, doesn't shared state from those closures let you shoot yourself in the foot in Go? You still have to be careful not to use anything not thread-safe that can potentially change...
Sure, but you always opt-in to using variables that are scoped outside of the current thread. You don't opt-in to the explicit control operators encouraged by the style where you don't use sequential statements -- they are required in order to program correctly. This is why I think the goto analogy is appropriate.
As of Go 1.2 (released a few weeks ago) goroutines are preempted. Sure currently this only happens in the presence of function calls, but it is hard to create a non preemptible infinite loop without function calls by accident.
This isn't a new thihg anyway, there are currently a lot of implementations, I even wrote one myself[1].
By the way, most (sane) libraries that use yield/await perfom a lot better, than vanilla promises. Because when you do .then() on already resolved promise you have to wait for the next tick, or setImmediate or whatewer. Yield-based implementations can continue right away in this case.
>Because when you do .then() on already resolved promise you have to wait for the next tick, or setImmediate or whatewer. Yield-based implementations can continue right away in this case.
Actually I learned to embrace next-tickness of promises in Q because I don't have to think twice about different possible execution order depending on promise's current state.
I would honestly prefer immediate continuation to be opt-in. This worked well in .NET (its Task's ContinueWith has a flag called TaskContinuationOptions.ExecuteSynchronously that tells the scheduler “you can run me right away if you like”).
If you use yield in JS you don’t have to think about execition order too. Because execution sequence will always be the same, and JS lacks real threading, so you won’t notice it anyway.
var x;
promise().then(function (result) {
x = result;
}).done();
x.doStuff();
This is a contrived example, and pretty stupid too. Still, if promises are always resolved on next tick, it will always fail. If promises may be resolved in the same tick, then you may miss the mistake.
A more real-world example is synchronization code like this[1]. Can you say at a glance if this code works fine both with resolved and pending promises? You'd have to check every `then`. You don't get this kind of problem with always-asynchronous promises.
In other words, it's a tradeoff between performance and introducing possibility of several different code paths, and it gets messier with non-trivial code.
The requirement is to always calls handlers with an empty stack and in a certain order. In practice only naivest implementations would call platform scheduler for every `.then()`. Implementations can in all cases call the handlers synchronously when it would matter.
> In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.
I did not say that one should not to comply to a specification. There is a big difference between implementations complying to specification and implementations where the source code is basically just duplication of the spec (which obviously runs very slowly).
So my point is that even if the spec might say that "Do A, then B and then C", that's not what an implementation has to do EXACTLY as long as the outcome is exactly the same in both cases.
For example a property load is a hugely complex operation in the ECMAScript spec that would take thousands of instructions to implement naively. In practice e.g. V8 will in many cases get away literally with one machine instruction in best case. None of the specified property load steps are obviously happening but yet somehow V8 is spec compliant.
Good but can you yield from the global scope? if not you still need to write at least a function.
The hardest stuff in JS is that :
function F(){throw "error"}
try{setTimeout(F,1000)}catch(error){ console.log("error")}
we cant catch async errors outside the async callback , will generators fix this ? ( by the way , that's why you should never throw any error yourself in js ).
You cannot yield from the global scope; you have to explicitly be inside a generator function. This isn't really a problem in practice except for one-off-scripts, since most modules don't have side effects but instead export functions (possibly generator functions). But for those one-off scripts it is indeed a bit awkward... you could imagine ways to get around it.
This might be not a very good example. But generators have more good features than just cleaner code.
1. You write code almost like synchronous one. You can use any control-flow statements you are familiar with. Just dont forget to turn a promise into real value with `yield` keyword.
2. How much times have you wrote such statements?
X.prototype.foo = function() {
var self = this;
// or
var that = this;
this.bar().then(function(x) {
that.baz(x);
});
};
or
}.bind(this));
With yield you can just write your logic, not a boilerplate:
X.prototype.foo = async(function*() {
var x = yield this.bar();
this.baz();
});
3. You are overvaluing control-flow constructs. Try-catch is always a catch-all error silencer where as promises can extend `.catch()` and `.finally()` to overcome flaws. Hand written loops are not that common, one mostly uses .filter, .map and .reduce in these kind of work flows.
1. You can easily write the same crappy code with promises anyway. And if you do think that `Promise.all` will save you, then you can use it with yield. With destructuring you even don’t need `.spread`.
Even more, with yield you can easily achieve parallel execution just moving `yield` keyword to the place where you need actual value. Good luck doing this with promises.
2. Unfortunately, traceur-compiler does not support fat arrow yet;
3. If you use try-catch just as a silencer, man I have bad news for you. And if you provide real error-handling in `.fail()` then what’s the problem writing the same code inside catch clause?
Well yes you can achieve better concurrent execution if you essentially duplicate the code. I also showed you how to do it with promises already (in 1 line) so not sure why you are wishing me good luck :)
There is just no way to use yield without it either being superfluous or sacrificing concurrency. Of course it is great when you have a sequence I guess, but promise code with arrows is not messy in comparison, maybe slightly more verbose at best.
Consider something like this which is far from optimal:
I said try-catch silences all errors that are not event meant to be handled but are bugs in code that should never be thrown in the first place. For example, if you have a typo in your code you will not know about that instead you will handle it like an expected error like network error.
Since promise `.catch()` is not limited by the language, you can do e.g. `.catch(NetworkError, e => ());` or whatever.
While I agree that using callback parameters instead of variable assignment is a bit fugly I think its not that bad and just takes think some time to get used to. For me, the thing that really benefits the most from generator syntax is structured control flow, specially loops: if you use generators you can use the existing JS for and while loop which is much better than doing the same with raw callbacks (manual recursion is basically gotos) or a an async library (promise-based or not, its going to be very verbose and still won't play well with "break" and "return" statements).
In the submitted article, two callback methods are usually provided (one for success and another for failure). Is that possible when using generators? If so, what does that look like?
Those are not entirely equivalent. For example, if you make a typo when writing "user" or "u", the first one will catch that typo (cannot get property name of undefined) while the second one will not.
Users.get(1234)
.then(u => u.name)
.catch(ex => 1/* do whatever */);
"Cross-platformness". Someone who wants to use your library and doesnt use harmony won't have to start his node process up with `--harmony` which is sort of a win I guess
As of now, you could use Promises but it is still kind of messy without generators. Here is a simple example:
We use FaceBook's regenerator project to compile ES6 features down to Node's currently supported Javascript features. If you don't want to use the --harmony flag, you should really try it out. https://github.com/facebook/regenerator