https://eladnava.com/write-synchronous-node-js-code-with-es6...
That said, for anything like a website or an API you probably won't need to worry much about channels or concurrency at all. The stdlib handles each web request on a different goroutine, so you write synchronous code, yet your server is concurrent.
http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-...
The basic idea is that you wrap the generator in a sort of trampoline that turns it into a promise. This 'trampoline' detects whenever the generator falls down to it (via some `yield` of some Promise) and then waits on that promise before resuming the generator-trampoline combo. The promise then returns its result directly into generator.next(), where it becomes the value of the `yield` expression, thus within the generator `x = yield myPromise()` creates a new Promise by calling myPromise, suspends execution until that Promise completes (by dropping down to the trampoline, which bounces us up into the myPromise().then()), and finally gets the value and assigns it to x.
You have just one scope, the scope of the function* of the generator, in which all of the results of all of your Promises live.
See:
[1] : http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
Manual promise transpilation is better than manual callback transpilation, but it's still manual transpilation. Why are you working so hard?
Sometimes I get mistaken as an advocate for Go. I'm not really. What I am an advocate for is using languages where you don't need an inner-platform of promises because the language is already that color. Go just happens to be the most familiar Algol-esque of the bunch right now. Don't want to learn Go? Go learn Erlang or Elixir. Or maybe if you're feeling saucy, Haskell. Or some of the other choices popping up lately. If you're writing network servers you will not be able to understood how you ever functioned without this.
The only excuse for mixed-style codebases is a codebase in the process of being converted from one style to the other.
[0] http://www.ecma-international.org/ecma-262/6.0/#sec-promise-...
function node(fn, ...args) {
return new Promise((accept, reject) => {
args.push((err, ...vals) => err? reject(err) : accept.apply(this, vals));
fn.apply(this, args);
});
}
What does this do? The argument of `new Promise` is a function which takes two functions, `accept` and `reject`, as its arguments. The function will be immediately run and is meant to do some stuff, possibly asynchronously, and then eventually call the first one of these on success or the second on failure. (One thing that takes a while to get used to about Promises is that generally the asynchronous thing starts when the Promise is created, so execution is not deferred ever.) We just take the args that it was called with, and append our own usual-Node-style callback to those arguments, asking it to pipe the Node error to the reject() function or the successful values to the accept() function.Then you need a sort of "trampoline" to convert a generator-yielding-promises into a single promise. That looks like this:
function go(gen_maker) {
var gen, goNext;
gen = gen_maker();
goNext = val => {
var item = gen.next(val);
return item.done? item.value : item.value.then(goNext);
};
return Promise.resolve(undefined).then(goNext);
}
Now what does this do? It defines a semirecursive function goNext which will hand itself to any new promises. The goNext function just runs the generator for one step with its argument, and looks at what it got out of that generator. If the generator has completed, it says "I'm done, great, return that last value!" and if it hasn't completed it says "okay, I have a Promise here, I need to tell it that when it's done computing what it's computing, it should goNext() with that value, to hand that value and control back to the generator." Finally after defining this magical function we begin the first gen.next(undefined) run by creating a Promise which immediately resolves to undefined, and telling it to goNext() with it. Of course, `gen` is the output `GeneratorFunction` that is generated by running a function* generator-maker on some arguments.With those two basics, you do eventually de-nest everything to just one layer of asynchronicity. You of course cannot do better than this to get truly synchronous code, but you might not need to if your last expression would have been undefined anyway.
For example, here is how you then write something which accepts two arguments and appends the first to the second, using a 10MB buffer in memory. (Yes, I know this is just `fs.createReadStream(process.argv[1]).pipe(fs.createWriteStream(process.argv[2], {flags: 'a'})`, with some messing around with the high-water mark; it's just to prove a point.)
// In this comment, we pretend node and go are defined as above, that we have
// checked for exactly two args on process.argv and logged some usage
// instructions if not, etc.
var fs = require('fs');
const buff_size = 10 * 1024 * 1024;
go(function* () {
var in_file, out_file, buff, count;
buff = new Buffer(buff_size);
in_file = yield node(fs.openFile, process.argv[1], 'r');
do {
out_file = yield node(fs.openFile, process.argv[2], 'a');
count = yield node(fs.read, in_file, buff, 0, buff_size, null);
yield node(fs.write, out_file, buff, 0, count);
} while (count === buff_size);
yield node(fs.close, in_file);
yield node(fs.close, out_file);
});
In terms of larger libraries that do the same, so that you don't have to roll your own `node` and `go` functions, see e.g. http://taskjs.org/ and some other similar projects.I see it a lot from backend developers who never really knew JS that well and then moved on to the next shiny thing (right now Go, but Haskell, Rust, etc) without ever having a good grasp of WHY they has issues - instead they blame node/JS.
In a year or so I fully expect all these articles to switch to "why Go sucks" as the cycle repeats.
Python for example is also compiled into *.pyc files, but it's still slower than go.
The real reason why Go would be faster than JS, is not as much about compiled/not compiled but statically typed vs dynamically typed.
In statically typed language the types are already predetermined early on (during compilation) so the code no longer has to perform type checking at runtime.
This "oh it's AOT compiled so it's faster" mantra is not accurate causation.