6 ms·
Let’s make a JavaScript Wait function
- hdhzy 9y agoI don't get what's the point of his article. Promises and async/await are here for ages. Moreover it does not highlight issues with await such as if you forget to await a promise you've just introduced a subtle but in your code. Sounds unlikely? Check out the last snippet in the article (waitForHello(2000) missing await, doesn't do any harm in this case as there are no code after it but it's a hidden bug).
- CodeWriter23 9y agoI also am confused by using the Pyramid of Doom as a pseudo rationale for needing this. Pyramid too tall? Use a function call. Nothing at all wrong with: function multiStepAsyncProcess() { hitServer(params, function() { multiStepAsyncProcessStep2(); }); } function multiStepAsyncProcessStep2() { hitServer(params, function() { ... }); }
- rmrfrmrf 9y agoNow do it with error handling and recovery.
- xg15 9y agoMoreover, async/await allows you to preserve multiple intermediary results between async steps - which is awkward in both promises and un-nested pyramids like the OP's: (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); } }
- CodeWriter23 9y ago<shrugs> ok function multiStepAsyncProcess() { hitServer(params, function(err) { if(err) { handleError(err); return; } multiStepAsyncProcessStep2(); }); } function multiStepAsyncProcessStep2() { hitServer(params, function(err) { ... }); }
- always_good 9y agoThat just "fixes it" with indirection, which is the only tool you have with callback style.
- CodeWriter23 9y agoActually I characterize it as opting out of newly-introduced madness, and using the techniques we used pre-madness. Not a “fix” so much.
- always_good 9y agoI can agree with that. 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.
- saas_co_de 9y agoYou are right. This has been a common error for me when writing code using async/await, but IMO it is not worse than forgetting to return promises in promise chains, so async/await is still a massive improvement over plain promises for me.
- throw401 9y agoThe problem with async await is that it is build upon promises, which sucked already from the start. 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?
- ledriveby 9y agoYes it is. Synchronous calls monopolize the event loop so everything - including page interactions - freeze until the control flow finishes busy-waiting. Yay, single threading.
- masswerk 9y agoOn the other hand, well-known constructs as used for translating FORTRAN labels to switch statements have been around for ages. E.g., 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); }; }
- masswerk 9y agoThis way we were porting former terminal services to web applications back in the last century ... (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.) :-)
- marcosdumay 9y agoEverybody solves that by having multiple threads (either at the OS or runtime level), so you can run in parallel the stuff that don't conflict with each other.
- _Marak_ 9y agoThe callback style pseudo code they posted as an example is not a good example of callback style JavaScript code. 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: http://caolan.github.io/async/docs.html#eachLimit http://caolan.github.io/async/docs.html#eachLimit https://github.com/tc39/proposal-async-iteration https://github.com/tc39/proposal-async-iteration
- masswerk 9y agoA bit OT, but may I suggest for a style guide to not use the shorthand, w/o brackets, in single-argument arrow functions, as in 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.
- ng12 9y agoIsn't that always the case? You always need to take into consideration the evaluation of the rhs. Consider: 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.
- masswerk 9y agoIt's really about the arrow operator starting with "=", which is most likely the beginning of a chained assignment as in 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.)
- saas_co_de 9y agoShould mention that Bluebird does this. http://bluebirdjs.com/docs/api/delay.html http://bluebirdjs.com/docs/api/delay.html
- iEchoic 9y agoYup, Bluebird has allowed for: 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.
- sparrish 9y agoPromises and Async/Await are just too slow. I'll stick with Callbacks. https://kyrylkov.com/2017/04/25/native-promises-async-functions-nodejs-8-performance/ https://kyrylkov.com/2017/04/25/native-promises-async-functi...
- sametmax 9y agoHow is that possible ? Await doesn't have this effect in python 3 and v8 is faster than cpython.
- saas_co_de 9y agoPromises require creating and modifying objects that contain the promise state which plain callbacks do not. The difference in micro-benchmarks can be huge: https://jsperf.com/native-promise-vs-callback 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.
- GhostVII 9y agoShouldn't we be more focused on which is easier to use though? Even if promises are slower, they probably aren't going to be a major performance issue, so I think if you find promises easier to use and less bug-prone, they are better regardless of preformance.
- sparrish 9y agoI don't think we should. I chose node over ruby because of performance. Those numbers are showing significant overhead for promises and async/await. That's a problem I don't want to inject into my latency-sensitive application.
- untog 9y ago> Shouldn't we be more focused on which is easier to use though? 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.)
- johnhenry 9y agoI made something like this a while back. https://github.com/johnhenry/asynchronous-delay https://github.com/johnhenry/asynchronous-delay. It's a useful pattern.
- johnhenry 9y agoAlso this: https://github.com/brummelte/sleep-promise https://github.com/brummelte/sleep-promise.
- lootsauce 9y agoThe longer I see people debate the value of promises and their obscured use via async/await vs callbacks I am more assured of the value of simple callbacks. If you are experiencing callback hell you have bigger problems than callbacks. If you love promises great! But please don't force them on me or I'll find another api to use thank you.
- always_good 9y agoI don't think any library authors care if you don't use their library. But I feel bad for everyone that has to touch your code.
- saas_co_de 9y agoI haven't heard or read a lot of debate claiming that callbacks are superior to promises and async/await. 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.
- rmrfrmrf 9y agoMultiple async vehicles can and will coexist in JS. IMO there’s way too much FUD about “one async story to rule them all”.
- deleted 9y ago[deleted]
- ajkjk 9y agoI love async/await, but it's not because it's "aesthetically pleasing". It's because having execution jump around from the outer body to the inner anonymous functions fucks with all my intuition and, especially, with the way debuggers work. 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.
- always_good 9y agoThinking that it's a historical accident that everything is async in Javascript is a fundamental misunderstanding of async-everything.
- ajkjk 9y agoWant to elaborate? I disagree, but I'm happy to be corrected. Anyway, what I said was that it's a historical artifact that JS has a single thread of execution, and by that I meant, "it was standard to do that at the time that JS was conceived, so that's how it was done". If you were inventing web browsers today I don't think you'd ever intentionally choose to do it that way.
- z3t4 9y agoI think jumping between functions makes JS fun :P It really clicked with me when I learned how closures work, and to use named functions.