Let’s make a JavaScript Wait function
hackernoon.com
hackernoon.com
function multiStepAsyncProcess() {
hitServer(params, function() {
multiStepAsyncProcessStep2();
});
}
function multiStepAsyncProcessStep2() {
hitServer(params, function() {
...
});
}(not even starting with conditions, loops and recursive calls that contain async steps...)
Async/Await:
async function f(a) {
var b = await foo(a);
var c = await bar(a, b);
return baz(a, b, c);
}
Promises: function f(a) {
var _b;
return foo(a)
.then(b => {
_b = b;
return bar(a, b);
})
.then(c => baz(a, _b, c))
}
Callbacks: function f(a, done) {
foo(a, b => bar(a, b, c => baz(a, b, c, done)));
}
Callbacks untangled: function f(a, done) {
var _b;
foo(a, step2);
function step2(b) {
_b = b;
bar(a, b, step3);
}
function step3(c) {
baz(a, _b, c, done);
}
} function multiStepAsyncProcess() {
hitServer(params, function(err) {
if(err) {
handleError(err);
return;
}
multiStepAsyncProcessStep2();
});
}
function multiStepAsyncProcessStep2() {
hitServer(params, function(err) {
...
});
}I think the problem with these sorts of discussions starts when someone dismisses promises, at which point one must assume that the person dismisses the problems that promises solve.
And I think that happens in the other direction too, when someone dismisses callbacks which suggests that they think that callbacks are intractable in all scenarios.
The first code posted is showing callback style with a nested loop over a directory listing to perform an image resize using graphics magick. Aside from the fact the author isn't actually calling any callbacks to indicate a complete event ( and won't be able to unless they have an async iterator ), there is a concurrency problem with usage of gm.
You really actually want to a run something like an async eachLimit in order to set the amount of open GM instances running concurrently or run the risk of running out of memory / cpu.
The second example using async/await doesn't mention anything about using async iterators either.
see:
var wait = ms => new Promise((r, j)=>setTimeout(r, ms))
Here, the semantics of "ms" are not resolved by its lefthand side, but only by its righthand side (i.e., if this was anything but an arrow, "ms" would be a value assigned to "wait", while here it's the beginning of a functional construct, spanning over the rest of the line.) Opposed to this, using brackets for arguments anytime conveys the meaning much more clearly: var wait = (ms) => new Promise((r, j) => setTimeout(r, ms));
Edit: The amount of look-ahead introduced by the shorthand syntax is rather foreign to JS, as is the switch in syntax that goes with the number of arguments (as illustrated by the example). I'm not convinced that this was the greatest idea in the history of JS syntax ever. var wait = ms * foo()
And that's not even considering the complexity introduced by the comma operator. I think the arrow is the key to visually identifying a function, not the parentheses. a = b = c = 0;
Also (as noted in the edit), I'm not the greatest of fans of the syntax switching with the number of arguments. In the example given, there are two arrow functions defined, each of them coming in a different appearance, due to the number of arguments used. Using always brackets, the nature of "ms" and it being used and passed as an argument would be more striking on first sight. Ease of understanding and readability aren't just luxury for a codebase.(The real difference as for pragmatics is this: In any other case, anything on the righthand side of the first "=" is a value resolved on first execution whenever the code is hit, while in this case it's a parameter that becomes evaluated whenever the function defined by this assignment is called – and this relation isn't resolved without look-ahead. Possible errors introduced by a sloppy (mis)reading are numerous.)
I wouldn't think so at all. I almost never do chain assignment in modern JavaScript but I write functions which return functions somewhat often.
I guess there's a possibility that someone quickly skimming code would misread => as =. It hasn't yet happened to me though.
You are probably right on this – but there's still old code around and ease of context switching may be a thing. Also, I guess, you can't be too defensive in your approaches, when setting up a shared codebase that may be around for some time. (With JS being extended on a yearly basis, we hardly can imagine what will be around a few years from now.)
Edit: However, also related to modern JS, another possible counter argument to mine: With the omission of terminating semicolons and frequent use of multiline statements determining the semantics has also become a question of scanning the next line anyway. Look-ahead is now a thing.
const plugin = store => next => action => foo(store, next, action)
vs. const plugin = (store) => (next) => (action) => foo(store, next, action)If I want a series of steps to execute in order, I should be able to write them down in sequential lines of code, and it's, imo, a historical artifact that I was ever not able to do that -- because (oops), JS was traditionally conceived to have sequential lines of code execute immediately after each other, instead of blocking until they're able to proceed.
That is, it's not required that "single threaded language" meant "non-blocking execution" and thus required "callbacks and promises to do asynchronous behavior". It just ended up that way -- and we're finally (almost) free of it.
For all those Promises proponents out there ready to burn me down, this is how you do it in C:
delay(1000);
And here the amazing Promises with await: var wait = ms => new Promise((r, j)=> setTimeout(r, ms));
await wait(1000);
Is it really that difficult to at least abstract this wait function away in the Javascript language?Also, why not? Do you see any problem with that? As long as their numbers are limited, I don't see any problem?
function main(entrypoint) {
switch (entrypoint) {
default:
case 0:
// do some (...)
// now drop the thread and wait for input (external)
myInputFunc(function(result) { main(1, result); });
break;
case 1:
// return to here from input
var value = arguments[1];
// now do something with it ...
break;
}
}
function myInputFunc(callback) {
// in actuality this might be servicing a more complex UI
document.querySelector('#inputField').oninput =
function() { callback(this.value); };
}(Maybe requesting additional server data by what would be called padded JSON nowadays via a hidden frame, because no AJAX. And, maybe, we would have rather used global vars for control than anonymous functions, but this another story.)
:-)
The biggest difference is that JS is designed to run in an event loop inside a single thread. If you perform a 10 second blocking I/O your thread will freeze. You don't want your browser/tab to be unresponsive. Or in the case of a web server you want to accept incoming TCP connections as fast as you can.
You can achieve parallelism with threads but it's very tricky to scale. If you want a simpler, threaded, blocking programming model I suggest you take a look at Go.
Edit: Definitely read the original slides from Ryan Dahl's JSConf 2009 presentation, they perfectly explain the point of using JS: http://tinyclouds.org/jsconf.pdf
But I feel bad for everyone that has to touch your code.
From the people I work with and everything I have read there is almost universal agreement that promise based code is easier to work with than callback based code and async/await is superior to both.
Callbacks do have superior performance, so if you are writing some really performance sensitive code then it might be worth using them, but like all optimization techniques that should only be done if the case truly requires it. Most don't.
https://kyrylkov.com/2017/04/25/native-promises-async-functi...
The difference in micro-benchmarks can be huge: https://jsperf.com/native-promise-vs-callback
That example is a probably a worst case scenario because of how it is constructed, but on my MBP it still gets around 1M promises created and resolved per second.
For a typical web app or api you will not see any benefit in using callbacks over async/await in your application code unless you are consistently doing something that requires 1000's of promises per request.
In JS I think this depends a lot on whether you are writing client-side code or not. Prioritizing developer ease of use over end-user performance is a very bad habit to get into. But it matters less when writing code that'll run in Node.
(that said, I'm not at all convinced that the performance difference mentioned in this article matters a whole lot. It's not actually that significant.)
Why is that? Whenever you use a high level language, you are making development easier at the cost of end-user performance. Or using a foreach loop instead of a regular for loop, is usually a bit slower, but makes development easier and less bug prone. Premature optimisation is the root of all evil and all that. Obviously at some point you have to prioritise end-user performance, when making decisions that will have a big impact on performance, but when making decisions on the level of promises vs callbacks, I don't think end-user performance should be a priority.
If that's still a non-negligible difference, I don't see why one wouldn't just write a server in something like go instead. Node isn't the best choice for a performance-sensitive application.
await Promise.delay(1000);
For as long as I can remember. I'm surprised Bluebird isn't more widely-adopted; it's been way better than native promises for at least 2 years now. The transform-async-to-module-method plugin with Bluebird is the way to go.