I affably don’t know how anyone considers the .then syntax to be any better than the original callback hell version. Especially if you want your error handlers to be unique and to affect the flow. Sorry but the common shared catch at the end doesn’t usually suffice in the code I write.
I find the async/await form he alludes to at the end to be the only form that improves upon the original callback version.
Async/await is syntax sugar on top of .then(); without the standardization of promises with .then(), async/await would have been A LOT harder to get cross projects. Ofc async/await is waaay better than .then(), but you were talking about async/await vs callbacks, and so my points :)
Agree. Async/await and promises get overly complex and riddled with conceptual challenges in anything but the most basic examples. I finally rolled my own async job queue library instead (using .then, granted). A few hundred loc, but conceptually clear, no callback hell, a tiny bit more verbose, but full control over error handling in each callback. Such a relief.
How does the resolve function work here? It is passed an object where one property is an object and the other a promise. The next then suddenly receives the resolved ___location..?
The argument passed to `resolve` gets returned from the resolve function and becomes available to any handler that handles it with `then`. In the above example, the object is being destructured as part of the arguments. The destructuring is probably what's tripping you up.
It iterates over all key/value pairs. For each value, if it is a promise it waits for it and replaces it with the result, while non-promises are left as-is. This is a fairly common function to have in promise libraries.
That was my point. It’s not obvious to the reader that this is better since it’s a non-standard way of resolving promises. But it looks neat. I’ve never seen it, what library do you have experience with that does it this way?
> In this example, both promises will be processed asynchronously and only when both of them are resolved, we handle the result.
While that is true if all the promises resolve successfully, if any of the promises get rejected the catch block gets executed as soon as the first rejection occurs. Beware of that behavior, as if you're not aware of it it can leave your system in a bad state (speaking from experience, of course :)
async/await helps a lot, but there are still a few common errors with them...
1) Not calling promises in parallel. Easy to do because it's impossible to run them in parallel with just "await", need to use Promise.all() or something.
2) Forgetting to write "await". If you try to use the return value then now you have a Promise object instead of the actual value. But the worst is when the code doesn't use the return value. Then it has a really subtle race condition that's hard to find. Or if the promise has any errors, then you might get the dreaded "Unhandled promise rejection" with a useless stack trace, and you might need to search the entire codebase to find where it happened.
It's easy to run tasks in parallel with just "await". Just start your tasks without await, store the promise in a variable, and then use await later when you actually need the value.
That's fine, but you'll still only be able to block on one promise at a time. If you want to wait until all promises have resolved you still have to use Promise. all.
Also sometime I don’t want promise.all but I do want them in parallel somewhat but with a max concurrency setting. Still have to pull out the old async.js package for that one and use it’s maxLimit.
I have found cases in which async/await won’t cut it quite as cleanly as “return new Promise”. AWS Cognito’s API has functions that take an object parameter with properties onSuccess and onFailure. If these functions are being called with programmatically created values within promises you can use “await new Promise” but that means declaring an additional async/await function (and therefore a promise) that resolves with the resolution of another promise. When you are explicitly defining all of your operations in TypeScript you notice these anti-patterns. Of course you could also declare these functions elsewhere and use function parameters instead of scoped variables. In my opinion the best solution is the one that is most justifiable when described to an outsider while walking through the code so I sometimes feel grateful for the freedom to choose my promise implementations on a case-by-case basis. When the relevant code is entirely my own, however, async/await is the most elegant.
Async/await is pure syntactic sugar and should not reduce errors unless the Promise syntax was implemented incorrectly.
I'm not sure I fully understand. Interoperability is as simple as wrapping the non-standard API, no? This way you have a standard calling style in your code. It's not a weakness of async/await that some libraries don't have promise friendly APIs.
The API in this example is promise-friendly, it is just intended for callback syntax; providing two situational callback options.
This an implementation issue. Of course you can wrap the non-conformant external code but in this case that means accepting anti-patterns in order to gain the benefit of syntactic sugar. Writing API wrappers that do nothing more than wrap traditional Promise callback code as a rule is a poor substitute for allowing developers to choose the most appropriate implementation. Instead that would favor meaningless promise chaining and unless you ignore a library’s type definitions that is pretty hard to miss. Using async/await isn’t doing anything different in the same sense that using a class is no different than using a function that returns an object. The question is one of readability and flexibility, no one syntax is inherently better that the other because the interpreter does not know the difference.
Async/await is inherently better than other syntax though, for a number of reasons, and the interpreter does indeed know the difference. If anything the interpreter knows much more about your intent, rather than “oh this function takes a `callback` parameter that’s a function” and can optimize based on that info.
It is not better, the two have different use cases. If you are using setTimeout to delay then you need a callback. Being so obsessed with the syntax that you right your code twice is the only wrong solution. While the AsyncFunction is used only by async statements and expression it uses Promise under the hood. In terms of performance it is not a good idea to make assumptions like that, performance can only be determined by testing.
> With basic understanding of Promises, these mistakes shouldn't happen
That's why blog posts like this exist, to teach people the basics. In the real world people often start using technology before they fully understand it, and then get sent blog posts like this to read (or catch them on HN).
Most of the time first thing i do is to wrap the library call in a promise. Callbacks are really bad after a few nested ones, bht promise doesnt eliminate all the problrms
_any_ function that returns a promise should have the `async` modifier - even if `await` isn't used.
A function may return a promise or throw an exception. An `async` function may only return a promise. async functions cannot throw exceptions.
```
// This code sucks but you might have to write it if `get` isn't an async function.
try {
get().catch(_ => /* handle async errors /)
} catch {
/ handle sync errors */
}
```
Interesting about adding async. I actually didn’t know it eliminated the possibility of it throwing an error. What happens if it explicitly throws an error?
They're all artificial examples: one could assume that the tasks in #3 are intended to be executed serially. Though there aren't any data dependencies expressed between the different calls, perhaps ordering could be important for other reasons.
I feel it should have been called out explicitly when introducing await, because the 'clean' solution in async/await code is to call each async function and then await the results where you need them - which is a pattern he doesn't hint at at all.
I think there is a danger in that approach: if you forget to await, errors are silently ignored. You can also await a Promise.all, which is reasonably good enough if you do depend on all of the results to do anything meaningful anyways.
If you need you can name your intermediate results fooPromise or something else to make it obvious that it isn't meant to be used directly.
The async article linked from this article has a toy example for this concept where the result is unused, but most of the time you would be passing the result somewhere. If you were using typescript it could fail at compile time when you try to pass a promise instead of the expected type. Even if you're not, you should notice it never work in your tests.
Sorry, by forget to await, I mean you call an async function but then don’t ever actually use the result. TypeScript can also diagnose that one by illuminating unused variables.
Still, I think it is easiest to never mess up if you await as soon as you have a promise value. In many common cases this is easy enough.
const userPromise = fetchUser(id)
const itemPromise = fetchItem(itemId)
// Do stuff, maybe even more async stuff
const item = await itemPromise
const user = await userPromise
Since those promises haven't been awaited until later in the code, they could throw and result in an unhandledRejection, which would be pretty bad. Promise.all is much safer since it instantly awaits both promises.
Lots of bikeshedding about minute differences in syntax. Especially #3 rubs me the wrong way, it is all syntactic sugar, the only tangible thing you get from all this is to prevent the code from working in older browsers.
I find Promises to be nothing but fancy looking syntax sugar over the callbacks. Infact, using the Async library one can write more cleaner and easy to understand code using callbacks as compared to Promises.
One thing I realized about async/await is that it removes the ability to use the synchronous continuation of an async function call. In return, it makes its asynchronous continuation feel synchronous. Technically, that's less power, but I realized that's almost always a good thing.
I don't understand what you're saying here. An async function just returns a Promise, that's it. You don't have to await on it immediately - you could just as easily assign it to a variable and await on it some time later.
I should really say `await`, in particular. But obviously, you can't `await` outside of an `async` function (proposed top-level `await` notwithstanding). It's true that you'll eventually get to the top of the `async` function stack and you'll have an actual Promise value, at which point, you're back in synchronous continuation land.
But point is that using `async`/`await` as much as possible restricts the programmer to less concurrency, which inevitably means fewer glitches and race conditions. Many people make the mistake of thinking that JavaScript is safe because there's only one thread. But it turns out that most of the hazards of concurrency still exist as long as continuations can interleave with access to shared resources and mutable state.
Does that make more sense? It's late over here, so I might not be super clear.