4 ms·
But 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 the
by Klathmon 7y ago
But 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.
- gbuk2013 7y agoYou are right - got mixed up in the responses - serves me right for trying to do too many things async! I would do it like this (if I didn't have access to "async/await" which is sadly the case for me in Node sometimes): function doStuff (foos, bars, bazs, callback) { // do stuff } function getStuff (callback) { let foos, bars, bazs; let semaphore = 3; function collector () { if (--semaphore) { return; } doStuff(foo, bars, bazs, callback); } getFoos((err, res) => { if (err) { return callback(err); } foos = res; collector(); }); getBars((err, res) => { if (err) { return callback(err); } bars = res; collector(); }); getBazs((err, res) => { if (err) { return callback(err); } bazs = res; collector(); }); } It is more lines of code but is boringly simple to read and allows for fully custom error handling (an example of the semaphore + collector pattern I mentioned below in another comment). The custom error handling would be in the individual "get" functions which allows for easier testing. The semaphore in the collector is the most tricky part and I would possibly replace it here with an "if" to check that all the variables have a value - depends on what sort of data I could get back.
- Klathmon 7y agowell IMO that is much more difficult to read for me. Promises are nice because they have a single purpose, to resolve or reject a promised value. So when I see them, I can pretty much instantly understand what it's doing. `Promise.all` tells me that it's waiting for all of them to resolve at once, `Promise.race` tells me that the result will be the first of them to resolve or reject. With callbacks, everything is "custom". So looking at your example I'd need to fully read the function to get an understanding of what it's doing, vs just knowing that Promise.all means "wait for them all and then resolve". Granted, if where you are working tends to avoid promises or heavily uses a language like golang where your style is much more common, then I actually completely agree with you that the callback version is going to be more maintainable, readable, and easier to work with in your team. But if/when your org/team collectively learns the "shorthand" that promises can provide, IMO they become much simpler to read and understand in most cases. It's basically a way of providing structure to really common patterns like yours, so that the actual structure fades into the background and the important stuff (what you are trying to do) becomes the only focus. In other words it's syntactic sugar. With all the benefits and drawbacks that any syntactic sugar provides.
- gbuk2013 7y agoI agree with you now that "async/await" is available (as I said in my article that I originally linked to). And thank you for providing a second use case to the one I listed in the article - it paid for all the time I wasted on HN today with this thread. ;) The little bit of magic that makes your code sane is the "await" in front of "Promise.all()" and makes it more elegant than the callback version.
- Klathmon 7y ago>The little bit of magic that makes your code sane is the "await" in front of "Promise.all()" and makes it more elegant than the callback version. I'd actually say that it's the `Promise.all` that makes it sane, the await could be replaced by a `.then` and be functionally the same. Actually it might be even easier to read to someone who doesn't live and breathe javascript as it will keep the program flowing top-to-bottom: Promise.all([a(), b(), c()]).then(([a, b, c]) => { // do stuff... }) As with most things javascript, there are 9 different ways of doing it, and 3 of them will shoot you in the face...