The problem is that this is a field that has been heavily studied, and there are great solutions to this problem that also solve countless other problems, and interop well with each other. The way promises work, they solve that ONE problem but throw away the rest.
If you have the choice between 2 mostly equivalent solutions to solve a problem, but one of the solutions also solve 10 other problems with essentially no drawback, why would you choose the former?
This is probably already linked in this thread somewhere and is also probably was prompted someone to post this old issue to Hacker news, but this is a good explanation of the problem:
Like? I am genuinely interested in knowing the drawbacks of promises and places where a callback like structure is more favourable. By the way, I am not contradicting you, just trying to learn.
Not sure I'd want to use a promise there :)
The issue with Promises is they could be a LOT better, and they're not for no good reason. Yeah, they're better than callback hell, but anything would be better than callback hell. "Hurts less than stabbing yourself with a screwdriver" should be a baseline expectation, not a recommendation. :)
I see this being said, but from what I've seen async handling is generally not very graceful; with that said, Promises are a very simple but powerful API that seem to handle the problem fairly elegantly.
That is why I'm surprised. I just don't know what the alternative is supposed to be! Can you point me to a language and a library that you would say does a better job of handling asynchronicity than Promises?
EDIT: To elaborate a bit, what I don't like about (JS) Promises, in hindsight, is that 1) they seem to infect my entire codebase, and 2) making non-async code async is usually nontrivial. In Elixir it's basically a matter of 'telling' a function or an entire module to go do it's thing in another process and report back when done. It means in many cases I only need to change the caller side of things and the code that runs async itself looks the same as the sync code. And that's not even the best part of (Erlang style) processes!
I lost several days struggling against the concept until realizing that Promises were intended and designed to work differently than every other aspect of the language.
Can you imagine the language stripping out a nested returned function and instead executing it and returning the value? That’s what Promises will do.
The only cleaner methods I’m familiar with are CSP (as implemented in clojure and go, and to some extent in JavaScript with libraries like RX and Bacon). Basically you’re treating steams of events as first-class values. That’s a rather functional/mathy way to treat concurrency/parallelism but I’ve found it illuminating.
I work a lot with databases, so say you're trying to write an object and its children in some sort of one-to-many relationship to the DB.
You can write a Promise chain like
startTransaction()
.then(() => writeParent())
.then((children) =>
Promise.all(children.map((child) =>
writeChild(child)
))
)
.then(
// handle success
endTransaction(),
// handle error
cancelTransaction()
)
What exactly about that isn't "proper error handling"? Can you expand a bit on how this would be handled differently by you, or perhaps what paradigm you're programming in where Promise.all executes a bunch of items that can't all have the same success and error handler?Are you suppose to instantiate the handler class and call it?
This way, you write the API request to just be a simple function invocation of the class you instantiated:
(req, res, next) => {
apiHandler = new APIHandler(args);
apiHandler.saveData(req.body)
.then(
// success
() => res.status(200).json(apiHandler.getResponse()),
// error
() => res.status(400|500).json(apiHandler.getErrorResponse())
)
I mean, it's just a pattern, and I wouldn't necessarily say I use it all the time; it's especially valuable for workers, where you have a series of functions you want to execute where some of them are going to be asynchronous.If you're just making simple create / update / get requests, it's less valuable, and you can just return a Promise from somewhere. Hopefully that gave you an idea of my motivations
A few years later, I use Promises a lot, and they work really well. The way errors bubble is logical and easily controlled, it's almost impossible to throw an unhandled promise exception, doing 'parallel' tasks is easy with ```Promise.all([])```, and there's pretty limited complexity with the way a Promise always returns a Promise.
I find the simplest way to use them is to define a class that has a few data properties you'd like to track.
It's modular and you know what to expect (it's always a promise!). Sure, it's imperfect, but I think its weakly opinionated simplicity is a good tradeoff. After all, it makes no distinction of whether any of the functions in that chain were synchronous or not, and that's a nice thing to know. It also makes testing with dummy sync functions in place of actually asynchronous ones a 0-effort integration.I don't know about this async / await keyword stuff, and if you have to wrap it in a try/catch block, that's unfortunate but also seems like it's not the end of the world. After all, isn't the Go language constantly requiring you to say "if err != nil <your code>"? Explicit error handling isn't even a bad thing.
Anyways, this is just my opinion, it's fair to have your own, and maybe other languages handle asynchronicity more gracefully, but from what I've seen this is a clean way to wrap async OR sync code in a context that makes it always behave predictably and readably.