This is literally 97.5% of all tech blogs, and a disappointing amount of content on HN front page.
I feel bad for the author. Nothing like asserting your ignorance on a blog. There's a time and a place for literally every language construct.
This article is bad, bad advice.
async function() {}
is much nicer than function() {
return new Promise(
(resolve) => resolve()
)
}Promise.resolve()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
You can use this in place of new Promise() (though you rarely need it, as an async function automatically wraps any non-promise return value in a promise.)
----------
Promise.all()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
For making requests in parallel, and returning when all have been successful.
----------
Promise.allSettled()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
For making requests in parallel, and returning when all have completed whether successful or not.
----------
Promise.any()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
For making requests in parallel, and returning the first successful response.
--------
As mentioned in sibling comment, things like fetch and DB calls return promises anyway, so the above are mostly only useful for working with multiple other promises.
async function getData() {
return new Promise((resolve) => {
const res = fetch(…);
resolve(res);
});
}
The response above is wrapped in THREE different Promises! One from fetch, one manually created, and one implicitly created by `async`.The code above behaves exactly the same as
function getData() {
return fetch(…);
}
or even just fetch(…);> usually junior front-end
So you agree with each other then?
I feel like there are advantages to making it `async function`, even if it's superfluous, because it signals to readers and to static code analysis that the function returns a promise. That's assuming the return type of fetch(...) can't be inferred by static analysis and developer tooling.
That is preposterous.
Would you say the same thing about
function getData() { return mysteryFunction(…); }
[0] https://www.youtube.com/watch?v=8aGhZQkoFbQ
[1] https://javascript.info/async
[2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guid...
It doesn't mean it can't be mastered, it can, but the whole paradigm is so fraught with pitfalls and conceptual difficulties that an codebase that uses it in any extensive way will forever be unstable. The whole thing is supposed to help against callback hell, but that can be better solved by a simple thenable object.
Not a popular opinion I know, but there you go.
That is NOT what async/await is about — after all, async explicitly marks functions as asynchronous. Not exactly trying to hide that.
Async/await is syntactic sugar. It is there to make working with Promises less verbose.
That is almost all: await pauses current function execution in the main event loop, which is an important detail
Wouldn't you simply be reinventing promises?
The usage of such an object won't be as terse and seemingly elegant as await syntax, but this is part of the problem: with await/promises so much of the complex logic is hidden from view and instead needs to reside the head of each dev, where it needs to compete with a thousand other things that need attention. It's an expression of the constant but unhealthy tendency towards golfing that pervades our field IMO.
With Node APIs, there's a promisified version of pretty much everything. With browser APIs, there's usually a version with promises, and I'm struggling to think of an asynchronous API without promises that hasn't been superseded by something else (e.g. XMLHttpRequest -> fetch). If I'm converting from an event-emitter API to promises, there's usually going to be an impedance mismatch between the expectations of the event-based system and the promise-based system, and I probably need to explore another option.
I agree that any competent JS dev should understand how to create promises "from scratch" like this. But it still should probably be a fairly rare occurrence, and if I see a lot of `new Promise` calls in one place, it's pretty much always a sign that someone doesn't really understand how promises work.
For starters you can just write:
const myfunc = () => new Promise(resolve => resolve())
or Promise.resolve() const myfunc = () => new Promise(resolve => resolve())
Is there anyone that actually likes this structure? I find it really hard to reason about what a line like this does. What is the upper limit on double arrows in one line? const wait = (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));
then: async function () {
await wait(1000);
}
Perhaps it's hard to grok but it sure is a handy one-liner.Of course in newer versions of node we can now do:
import {
setTimeout,
} from 'timers/promises';
await setTimeout(100);const wait = require(‘util’).promisify(setTimeout)
async function process() {
return new Promise((resolve, reject) => {
emitter.on(”error”, (err) => reject(err));
emitter.on(”end”, (result) => resolve(result));
emitter.start();
});
}
async is not strictly needed here, but I find it a good practice to use it on functions which immediately return a Promise anyway. async function foo(bar) {
// ...
}
function nonObviousAsyncFn() {
// ...
return foo(quux);
}
Explicit return types would help, but most people don’t write them unless they’re forced by a linter. return await foo();Sounds like you didn't understand the point of the article. Unfortunately, you're arguing against something the author never said, the author never said that async/await weren't promises. You just got stuck on the fact that the author used the term "promise" for the non-async/await style of code. I understood what the author meant, anyway.
> "People can mess this up" was never an argument
Unfortunately, you're arguing against something the author never said here, also. The author is saying that non-async/await code is better code than async/await. Not that "people can mess this up" or something.
Well. I never said that I didn't understand the article. Unfortunately you are arguing against something I never said. Funny how that works.
> The author is saying that non-async/await code is better code than async/await.
And what does "better" mean to the author?
The entire first section is devoted to the author talking about criteria such as "brittle"-ness, "error-prone"-ness, and "footguns".
You seem to be getting stuck up on the fact that the author never used the exact same wording as I did.
Indeed it is not the point, and in that sense your criticism of the article is correct.
However, there are people who argue[1] that it is, in fact, a good thing to never have the word "Promise" appear in your code, that not having the freedom to execute whatever code you want after launching an asynchronous operation and instead having the predictability of always returning to the scheduler makes reasoning about asynchronous code much simpler. (For one thing, you can now reliably think—and have syntactic support for thinking—in terms of sequential coroutines with predictable yield points and known parents.)
In the context that Smith refers to (trio vs asyncio in Python, regarding which also see the first, more Python-specific but IMO better-argued post[2]) my experience actually bears that out. How well this works given the historical API baggage in JavaScript, I don’t know, but probably not very well.
Another curious thing is that the line of research JavaScript promises originate from[3] (Mark Miller co-wrote the spec proposal IIRC) does not use them as the user interface; instead, using the "eventual send"[4], you queue up method calls to objects (which can be promised results of other queued calls, and you may pass other promised objects as arguments).
I cannot clearly articulate the relation of this to Smith’s notion of “structured concurrency” (I wish I could), but in any case it contributes some more weight to the opinion that the ergonomics of Promises are indeed subpar. It might well be, though, that in the context of JavaScript you can’t actually enforce enough structure on the preexisting APIs to build a superior alternative.
[1] https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
[2] https://vorpus.org/blog/some-thoughts-on-asynchronous-api-de...
I would even say that async/await is anti-promise, it takes the main functionality of promises, a caching layer for results and errors that allows you to add the code continuation later and elsewhere (which is a major footgun imo) and coerces the execution flow back to going on the next line and provided immediately at compile time which results in a cleaner flow but not as clean, stateless, efficient or functional as if you were to remove the promises completely. Having an additional caching layer and state machine around every asynchronous function call is quite inefficient.
The essence of async/await is not promises, it's the underlying javascript generator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...) functionality combined with asynchronous code to stop and start the generator. It's the ability to pause and resume function execution based on asynchronous operations.
The promise functionality, the caching layer and state machine for results is basically sanitized away with async/await, it becomes dead-weight computation. The only benefit of promises in async/await code is being able to more easily interface with other promise laden code which you don't need once you have async/await and a library like https://www.npmjs.com/package/async for more complex cases.
Note that promises based async/await is also a mess of an implementation that breaks stack traces and needs to support tons of odd statement corner cases (basically anything that can return an object that could be a promise) whereas a continuation passing style async/await would be a much simpler implementation that would only apply to function calls and maintain stack traces. We would get that stack trace support automatically because of the great work of whoever implemented javascript generators which seem to already carry stack traces across paused/resumed functions (if you don't wrap in promises).
My feeling is that mixing the use of promise chains and async/await can make code hard to follow, so I ask colleages to not mix them in the same function.
Sure, async/await is syntactic sugar for a generator that chains promises into a promise chain for you. Not sure why you'd care. That is an implementation detail that never gets exposed to the user.
The only time I feel that promise chains are better async/await is when treating Promise as a monad https://blog.bitsrc.io/out-with-async-await-and-in-with-prom...
A lot of the argument appears to be the author extrapolating from his own lack of familiarity to others: "We are taught", "our minds", etc. I can easily construe some hypothetical person with a certain set of skills (and lack thereof) that would have just as much trouble with bare-bones Promises. But I don't have to because the author did it for me at "One more thing…".
"People can mess this up" was never an argument. You can mess everything up. The more interesting question is how badly and how often.