Recently I've created a project to help people deepen their knowledge on Promises in Javascript beyond the basics by working through a series of practical exercises, where each is accompanied by a set of automated tests.
One thing I have recently stumbled upon is that the way Promise.all() is commonly used is "wrong" in the sense that it is prone to uncatchable promise rejections (which cause both Node and Deno to exit the whole process by default).
I would really like to see a tutorial on how to solve this the right way -- right now it seems that everybody does it the "wrong way" and the problem isn't even mentioned.
Common pattern #1 (tuple-style):
const result = await Promise.all([
someAsyncFunction1(computeArg1()),
someAsyncFunction2(computeArg2()),
]);
Common pattern #2 (array-map-style):
const result = await Promise.all(elements.map(x => someAsyncFunction3(computeArg3(x))));
Both suffer from the same problem: If computeArg2 / computeArg3 potentially throw a (synchronous) error, then some promises have already been created, but Promise.all() never runs and so they don't have a catch-handler. If one of those rejects ("throws asynchronously") then that rejection is unhandled and crashes the process.
allSettled() returns an array of objects for each promise result that tells you if it has been rejected or resolved, so as long as all the promises reject or resolve then it should solve the problem I think?
That one is super interesting. For the first example, I'm not even really sure how I'd expect that to behave, it feels like no matter how it works it becomes slightly inconsistent with my expectations of the language. Like, if it were changed to somehow let a .catch grab the synchronous errors or have it somehow evaluate all of the compute functions synchronously first before making the promises, it still feels wrong. Maybe the only good solution is run the compute args separately beforehand?
For the second example, it's kind of a weird one, but I'm actually a fan of using async .reduce to accomplish that. It took me a minute the first time I saw someone do it, but I think it more cleanly solves the problem and gives you more control over the result.
Reduce can use an async function which wraps the collector in a promise and gives you a ton of control over how it executes because you can choose at what point you want the function to block waiting for previous results.
In this particular case it's a lot more verbose, obviously, but if you're doing any kind of further processing of each element, you can move it into the reduce and be able to control exactly what you get out of it if something fails for any reason, while still running mostly concurrently like all or allSettled would.
const result = await elements.reduce(async (collector, x) => {
const arg3 = computeArg3(x) // explicitly handle whatever errors, try catch, whatever you need
const asyncResult = await someAsyncFunction3(arg3)
// this has to happen after calling someAsyncFunction because it will block until the previous iteration finishes
collector = await collector;
collector.push(result)
return collector
}, []);
The answer is to never throw exceptions from promise returning functions. Mark all promise returning functions `async` - even when it doesn't `await` - because an async function always returns.
I think that's just meant to mean that cloning the repo isn't part of the tutorial. For the tutorial, you're meant to use the "npm create promises-training@latest" command to get the correct parts of the repo.
The way it's formatted and lacking this context is definitely a bit too aggressive, as if you'll get in trouble with someone if you do want to clone the repo for other reasons.
It's as @AgentME said, it is just meant as a disclaimer that the project should be installed via the "installer", i.e. `npm create promises-training@latest`, otherwise the project won't work as expected.
But I agree the phrasing is a little bit weird, will review it.
Currently, promises are the de-facto way of handling asynchronous tasks in Javascript
Not really. Most of the Node API is still reliant upon callbacks and the frontend absolutely doesn’t care. With one technical exception[1] the use of promises is purely stylistic. As someone old I still prefer callbacks because it’s just functions.
One of the biggest frustrations with JavaScript is people attempting to impose their opinions on you. If you prefer promises then just use promises. The language provides 3 options for asynchronous handling and doesn’t care what path you take.
[1] A promise chain cannot be cancelled or exited early without a throw.
Okay, but nothing built in to the standard. In fact, even the docs you linked only point out that other implementations of Promises are "then-able". I guess I was under the impression that there were other, non-Promise, examples of thenables
The main use I've seen is returning "promise-like" objects in functions that can be sync or async depending on params but are expected to have a common return type.
To add to this, async/await is not a complete replacement. As in, it doesn't mean you'll never have to write `Promise` in your code. You still have to rely on Promise.all when you want to run multiple promises concurrently instead of sequentially
The language provides Promise objects[0]. Async/await is just syntactic sugar around Promises - every await awaits a Promise object, every async returns one.
Also, it's a bit weird to say callbacks -vs- promises. Callbacks aren't inherently async in themselves; there are some native async APIs that accept callbacks - the callbacks themselves are just functions, its the native APIs that are async. Also, Promises take and execute callbacks. We're still using callbacks.
What Promise objects did (for the first time) was give programmers the power to implement async functionality in the native language themselves, outside of native API calls to external functionality.
Differences between promises and callbacks is not the concern here. There are three options for asynchronous handling whether or not they are different under the hood and regardless if this is all just vanity.
What Promise objects did
Callbacks already did that. Its just moving one concern from an API to a method. Nonetheless the capability was already there natively in the language, which is called the event loop.
In all things, even outside of code, people express vanity as an implicit means of advertising some manner of their identity or preferences. To the contrary people impose vanity on others only as a means of compensating for their own low confidence. Confidence is generally exceptionally low in software.
That's perfectly fair but you may be in a bubble if you really believe your preference reflects the general tendency of the community.
Yes, a lot of Node's API is still reliant upon callbacks, but newer APIs where promise support has been added are generally preferred, and outside of those, most people rely on library abstractions anyway - most of which wrap Node's internal APIs in promises. Bluebird was around since 2013 & was very popular with the NodeJS community.
> it’s just functions
So are promises really. There's very little effective syntactic difference between `const x = new Promise((func, func) => {});` and a function accepting callback(s) - the main differentiator is a separate error callback instead of the error-first argument convention. Yes the internals are different to allow assignment & chaining without nesting but nesting is still just as possible if preferred.
But yeah if you prefer callbacks go ahead. Just make sure the team you're collaborating with is reasonably aligned on preference.
You said “Not really” in response to the claim that “Currently, promises are the de-facto way of handling asynchronous tasks in Javascript.” So I guess you care.
Reading comprehension helps. I said that promises are not really defacto standards. They are a standard convention of the language and one of three ways of handling asynchronous actions.
I understand that some parts of the Node.js API does not use promises. I'm saying that promises are ubiquitous in Node.js codebases.
The original comment said that promises are "not really" used in Node.js codebases. This is blatantly false. Most, if not all Node.js codebases I've worked with all use promises. And if they still use callbacks... you better believe I'm moving them to promises :)
Promises are not hard at all. I'm surprised there's such a pushback. I've been using async/await since like 2018.
De-facto doesn't mean it's the only option, it means it's the dominant option, and that is true.
Promises don't have a built-in way to cancel chains (neither do plain callbacks), and I think that was the right decision. AbortController is becoming a common pattern, and that is working quite well in my opinion.
Promises are not purely stylistic. It gives you guarantees, such as that each Promise can only resolve or reject once. It can easily be used in utility functions such as Promise.all and Promise.race. There are libraries that have equivalents for callback-style APIs, but it's typically more tricky to use, and relies on specific conventions being followed.
NodeJS took a while to adopt it, but all Node APIs I use now have Promise versions. All/most Deno APIs use Promises. All new browser APIs use Promises, and for the existing callback-style ones such as IndexedDB, most developers use Promise wrapper libs to make it manageable.
Of course you're free to use callbacks of you want to. But I would definitely recommend Promises and async/await to any new developer, make it a requirement on any team I lead, and even question why someone uses callbacks in their code if I interview them.
Promises don't have a built-in way to cancel chains (neither do plain callbacks)
That is a false comparison. A function is not comparable to a promise chain. A chain of callbacks can be cancelled by simply returning early in one callback instead of calling or responding to a forthcoming additional event.
Not all Node APIs have promise methods regardless of the few you use personally. This sounds more like people trying to impose their vanity opinions. The mentality of: It sometimes works for me, so other people are wrong if they don't do it the way that matches my preferences.
and even question why someone uses callbacks in their code if I interview them.
Why would you pose the question if you already biased against the answer? Is this an attempt to prejudice a candidate?
> Why would you pose the question if you already biased against the answer?
Why would you pose _this_ question if you are already biased against the answer? :)
But to answer your question:
1. to question your own bias. You might get an answer you didn't expect that actually helps you gain new insight.
2. to inform the candidate that good reasons are needed to keep their style if they want to work for you. "I prefer my own style because I'm used to it" creates single points of failure, while "I prefer this style because of X" improves code quality.
I see what you mean about promise chain cancellation, I misunderstood that part. You can still structure Promises using nesting instead of chains to get the same effect as with callbacks, but in reality I just use async/await instead.
> Why would you pose the question if you already biased against the answer? Is this an attempt to prejudice a candidate?
By question I mean I may judge the candidate for it. Of course it's your choice if you prefer callbacks, but I'd view that as a potential compatibility issue with my team.
Complementing your answer, I'd also like to point out that another notable guarantee that promises give us is that their continuations are never executed synchronously (even when they could), which prevents the problem described by https://blog.izs.me/2013/08/designing-apis-for-asynchrony/.
Someone has gone through a massive effort of creating this. I'm guessing that he's all in for constructive criticism. Asking for something else and offering only "The writing feels a bit off to me" isn't very helpful or polite IMHO.
I apologize to the author if my comment comes across as impolite.
I really wanted to bookmark it for further reading since its a topic I’ve been meaning to dive deeper into. I was happy that the target audience wasn’t beginners since it assumes familiarity with promises and async/await.
But I kept parsing the numerous “warnings” and the writing style in a way that made me feel that perhaps I’m not the target audience.
I’ve come to realize that I am much more likely to make the most out of content when the writing style gels with how I think.
Not the GP but it could be the length of your sentences. I personally think it reads fine however, since I also write with pretty long, heavily punctuated, sentences I’m comfortable reading it and it feels natural to me. The biggest complaint people have about my writing is the length of the sentences and feeling like I’m expressing too many ideas at a time.
Three Firefox developers from three different teams (Paolo, Irakli and I) each came up with Promise (with slightly different name and semantics - I think that mine was called Future, for instance). We were using them behind the scenes to refactor Firefox and make its UX async. Of course, when we discovered that there were three implementations, we merged them.
Later, Irakli led conversations with (if my memory serves) two other teams who had also come up with Promise on the web or on Node, and we eventually merged with these proposals.
Similarly, my team was using async/await in 2014 or 2015 (it was called `Task` at the time), a few years before it was standardized. I believe that Paolo was the first person in the world to ship production code with async/await and me the second.
68 comments
[ 4.6 ms ] story [ 143 ms ] threadI would really like to see a tutorial on how to solve this the right way -- right now it seems that everybody does it the "wrong way" and the problem isn't even mentioned.
Common pattern #1 (tuple-style):
Common pattern #2 (array-map-style): Both suffer from the same problem: If computeArg2 / computeArg3 potentially throw a (synchronous) error, then some promises have already been created, but Promise.all() never runs and so they don't have a catch-handler. If one of those rejects ("throws asynchronously") then that rejection is unhandled and crashes the process.The problem is also discussed here, but in the context of "should ECMAScript be changed": https://es.discourse.group/t/synchronous-exceptions-thrown-f...
For the second example, it's kind of a weird one, but I'm actually a fan of using async .reduce to accomplish that. It took me a minute the first time I saw someone do it, but I think it more cleanly solves the problem and gives you more control over the result.
In this particular case it's a lot more verbose, obviously, but if you're doing any kind of further processing of each element, you can move it into the reduce and be able to control exactly what you get out of it if something fails for any reason, while still running mostly concurrently like all or allSettled would.
https://typescript-eslint.io/rules/promise-function-async/
Then what is the issue described in #1?
Ahh, the compute arg calls
> ATTENTION: DO NOT CLONE THIS REPO UNLESS YOU'RE CONTRIBUTING
The license is Creative Commons so this doesn't really make any sense.
The way it's formatted and lacking this context is definitely a bit too aggressive, as if you'll get in trouble with someone if you do want to clone the repo for other reasons.
But I agree the phrasing is a little bit weird, will review it.
Not really. Most of the Node API is still reliant upon callbacks and the frontend absolutely doesn’t care. With one technical exception[1] the use of promises is purely stylistic. As someone old I still prefer callbacks because it’s just functions.
One of the biggest frustrations with JavaScript is people attempting to impose their opinions on you. If you prefer promises then just use promises. The language provides 3 options for asynchronous handling and doesn’t care what path you take.
[1] A promise chain cannot be cancelled or exited early without a throw.
The language actually just provides one: async/await.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Also, it's a bit weird to say callbacks -vs- promises. Callbacks aren't inherently async in themselves; there are some native async APIs that accept callbacks - the callbacks themselves are just functions, its the native APIs that are async. Also, Promises take and execute callbacks. We're still using callbacks.
What Promise objects did (for the first time) was give programmers the power to implement async functionality in the native language themselves, outside of native API calls to external functionality.
[0] https://tc39.es/ecma262/multipage/control-abstraction-object...
What Promise objects did
Callbacks already did that. Its just moving one concern from an API to a method. Nonetheless the capability was already there natively in the language, which is called the event loop.
In all things, even outside of code, people express vanity as an implicit means of advertising some manner of their identity or preferences. To the contrary people impose vanity on others only as a means of compensating for their own low confidence. Confidence is generally exceptionally low in software.
That's perfectly fair but you may be in a bubble if you really believe your preference reflects the general tendency of the community.
Yes, a lot of Node's API is still reliant upon callbacks, but newer APIs where promise support has been added are generally preferred, and outside of those, most people rely on library abstractions anyway - most of which wrap Node's internal APIs in promises. Bluebird was around since 2013 & was very popular with the NodeJS community.
> it’s just functions
So are promises really. There's very little effective syntactic difference between `const x = new Promise((func, func) => {});` and a function accepting callback(s) - the main differentiator is a separate error callback instead of the error-first argument convention. Yes the internals are different to allow assignment & chaining without nesting but nesting is still just as possible if preferred.
But yeah if you prefer callbacks go ahead. Just make sure the team you're collaborating with is reasonably aligned on preference.
Who cares? Why are you trying to tell me what my opinion on code preference is?
https://owl.excelsior.edu/argument-and-critical-thinking/log...
So are promises really
Absolutely not. Promises are a separate convention of chained .then methods.
You seemed to as you commented to that effect.
> Why are you trying to tell me what my opinion on code preference is?
Did I?
> Who cares?
You said “Not really” in response to the claim that “Currently, promises are the de-facto way of handling asynchronous tasks in Javascript.” So I guess you care.
This is not true at all. Promises and async/await are ubiquitous.
* Library with/without promises - https://nodejs.org/dist/latest-v21.x/docs/api/fs.html
* Library without promises - https://nodejs.org/dist/latest-v21.x/docs/api/net.html
The original comment said that promises are "not really" used in Node.js codebases. This is blatantly false. Most, if not all Node.js codebases I've worked with all use promises. And if they still use callbacks... you better believe I'm moving them to promises :)
Promises are not hard at all. I'm surprised there's such a pushback. I've been using async/await since like 2018.
Not really. Most of the Node API is still reliant upon callbacks and the frontend absolutely doesn’t care.
Promises don't have a built-in way to cancel chains (neither do plain callbacks), and I think that was the right decision. AbortController is becoming a common pattern, and that is working quite well in my opinion.
Promises are not purely stylistic. It gives you guarantees, such as that each Promise can only resolve or reject once. It can easily be used in utility functions such as Promise.all and Promise.race. There are libraries that have equivalents for callback-style APIs, but it's typically more tricky to use, and relies on specific conventions being followed.
NodeJS took a while to adopt it, but all Node APIs I use now have Promise versions. All/most Deno APIs use Promises. All new browser APIs use Promises, and for the existing callback-style ones such as IndexedDB, most developers use Promise wrapper libs to make it manageable.
Of course you're free to use callbacks of you want to. But I would definitely recommend Promises and async/await to any new developer, make it a requirement on any team I lead, and even question why someone uses callbacks in their code if I interview them.
That is a false comparison. A function is not comparable to a promise chain. A chain of callbacks can be cancelled by simply returning early in one callback instead of calling or responding to a forthcoming additional event.
Not all Node APIs have promise methods regardless of the few you use personally. This sounds more like people trying to impose their vanity opinions. The mentality of: It sometimes works for me, so other people are wrong if they don't do it the way that matches my preferences.
and even question why someone uses callbacks in their code if I interview them.
Why would you pose the question if you already biased against the answer? Is this an attempt to prejudice a candidate?
Why would you pose _this_ question if you are already biased against the answer? :)
But to answer your question:
1. to question your own bias. You might get an answer you didn't expect that actually helps you gain new insight.
2. to inform the candidate that good reasons are needed to keep their style if they want to work for you. "I prefer my own style because I'm used to it" creates single points of failure, while "I prefer this style because of X" improves code quality.
> Why would you pose the question if you already biased against the answer? Is this an attempt to prejudice a candidate?
By question I mean I may judge the candidate for it. Of course it's your choice if you prefer callbacks, but I'd view that as a potential compatibility issue with my team.
Complementing your answer, I'd also like to point out that another notable guarantee that promises give us is that their continuations are never executed synchronously (even when they could), which prevents the problem described by https://blog.izs.me/2013/08/designing-apis-for-asynchrony/.
> .then(() => new Promise(() => {}))
A promise that never resolves is effectively an abort.
The writing feels a bit off to me.
I really wanted to bookmark it for further reading since its a topic I’ve been meaning to dive deeper into. I was happy that the target audience wasn’t beginners since it assumes familiarity with promises and async/await.
But I kept parsing the numerous “warnings” and the writing style in a way that made me feel that perhaps I’m not the target audience.
I’ve come to realize that I am much more likely to make the most out of content when the writing style gels with how I think.
If it makes you feel more comfortable, you may DM me here or any other social network (e.g. Github).
I did the concrete exercises instead which was straight forward.
Later, Irakli led conversations with (if my memory serves) two other teams who had also come up with Promise on the web or on Node, and we eventually merged with these proposals.
Similarly, my team was using async/await in 2014 or 2015 (it was called `Task` at the time), a few years before it was standardized. I believe that Paolo was the first person in the world to ship production code with async/await and me the second.