4 ms·
> This is one of my pet peeve with proponents of promises actually. They come up with convoluted examples to argue against callbacks but it's not the callbacks
by koolba 7y ago
> This is one of my pet peeve with proponents of promises actually. They come up with convoluted examples to argue against callbacks but it's not the callbacks that are a problem, but the person writing the code.
While I agree the author’s examples are convoluted, the async-await code is much easier to read, write, and validate. Why bother tracking state and having to keep tracking of JS lexical scoping when you can write "boring" code that will be just as fast for the tiny iteration counts you'll be dealing with?
> Even with "await", the only time when it is really useful is when there have to be a several asynchronous operations that must happen serially because they use the result of the previous call. It depends on your field of work, but in my experience these situations are really no that common.
Concurrent tasks are more pleasant with await as you can combine it with Promise.all(...) to get efficiency and legibility:
async function doStuff() {
// Fetch things concurrently
const [
foos,
bars,
bazs,
] = await Promise.all([
getFoos(),
getBars(),
getBazs(),
]);
// Do stuff with them...
}
- gbuk2013 7y ago> the async-await code is much easier to read, write, and validate. With the exception of necessarily serial code that I referred to, this statement is not true in my experience. Your "legible" example is only legible because you are not doing anything actually important like error handling and logging. In the real world each call may need different logging or even error handling logic and that is when promises really turn into a rats nest. Validation, i.e. tests, are really the same regardless which approach you are using. I do agree that async/await made promises actually usable, as I said above. For running a bunch of asynchronous functions that do the same thing the semaphore + collector pattern works just fine and is not much more verbose.
- Klathmon 7y ago>Your "legible" example is only legible because you are not doing anything actually important like error handling and logging. async function doStuff() { try { // Fetch things concurrently const [ foos, bars, bazs, ] = await Promise.all([ getFoos().then((res) => { console.log(`getFoos done with ${res}` return res })), getBars(), getBazs() .catch((err) => { // handle only the error in getBazs }), ]); // Do stuff with them... } catch (err) { // handle any errors } } it still isn't what i'd call "pretty" code, but it's simpler for me to read and comprehend than doing most of those things without async/await or Promise.all or other helpers like that.
- koolba 7y agoI was about to write up that exact example of the nested handlers.
- Klathmon 7y agoI actually love slinging promises like that, but I tend to avoid it in most cases as there are MANY devs that would be somewhat rightfully frustrated at the code I just wrote. As always, who you are working with matters a LOT, and if I were on a team where a good portion of the devs weren't comfortable with that style of code, then I'd use more verbose ways of doing it that are more inline with what they are expecting. But on the rare occasion that the complexity necessitates the advanced promise stuff, or in jobs (like my current one) where all of us devs are super comfortable with promises being used like this, then it's a dream to work with them!
- gbuk2013 7y ago(see below)
- Klathmon 7y agoBut those 2 examples aren't doing the same thing. My Promise.all example will fire off the getFoos, getBars, and getBazs requests all at the same time, and then do things at different moments with the results in some cases. Yours will do them serially like this: getFoos -> wait for it to resolve -> run getBars -> wait for it to resolve -> run getBazs -> wait for it to resolve -> run doStuff And if individually handled errors is what you are after ala go, then this also does it while preserving the parallelism: async function doStuff() { // Fetch things concurrently const [ foos, bars, bazs, ] = await Promise.all([ getFoos().catch((err) => { // handle only the error in getFoos }), getBars().catch((err) => { // handle only the error in getBars }), getBazs().catch((err) => { // handle only the error in getBazs }), ]); // Do stuff with them... } And I'm not suggesting we always use promises in every area, but that for some things they are the perfect tool for the job. There are some things that promises are really bad at that callbacks do really well. One example is progress systems. A callback can be called multiple times over the course of an async call with percentage it's completed every call. A promise is one-and-done, and that's a big issue in many areas. But that's why callbacks aren't really "deprecated" as much as they are relegated to doing what they do best, while leaving things that Promises and async/await do best to them.