Promises are being added to everything. Browser API’s, the latest cool frameworks and libraries, browsers, and soon, maybe even node core.
In theory, I’m all for it. In theory, if we had a clean, simple way to describe asynchronous interactions, it’d be a great idea. In reality, Promises fall short of the mark, and their relationship with async-await is one for concern.
In theory…
The best parts of JavaScript have been the result of goat tracks, a path beaten by thorough usage. Promises started getting used in some form of anger in libraries like jQuery, and in the cases for which they were being used, they pretty much did everything developers needed. Want to load a file and then parse it? Easy. Want to load a file, then load a few files based on the first? No problem. Throw an error? Cool, just show an alert(). For trivial use cases, Promises look great.
But there are downsides that make promises not very pleasant to use.
Bother.
They catch thrown errors and handle them the same way as rejections. This is a massive pain if you want your server to crash when you’ve made a coding mistake, as you have to try and detect ‘good’ errors, like 404s to external services, or DB lookup misses, from ‘bad’ errors like “TypeError: 555t5tt55555555 is not a function” – something that occurs frequently when you own cats. There is discussion around modifying how try-catch works in JavaScript to work around this issue, but that should be a massive red-flag in its own right.
Promises also don’t interop with existing API’s nicely, you have to write boilerplate to interact with the normal ‘error, result’ pattern.
Promises run eagerly. By its self this isn’t an issue, there are cases where this is good, but often, lazily requesting asynchronous dependencies is preferable. Creating a lazy dependency graph also allows for easier debugging and description.
What then?
There is nothing special about Promises, they were just big first. There are many alternatives that do a better job. righto and pull-streams do everything Promises do, with fewer pitfalls. Righto interacts with err-backs, and promises, with no boilerplate, won’t catch thrown errors, and can even print a trace that shows the dependency graph for a flow, and which exact task rejected.
But Mah Async-Await!
The biggest issue with not using Promises that get’s raised is that they are functionally required for async-await, but they aren’t. There is nothing specific about promises that makes them necessary for async-await to work; any async task wrapper could be used just as well. Async-await supporting Promises exclusively is a conflation of concerns.
If async-await was more generic, it would be possible to use it with a wide array of third party libraries that expose the right API. pull-streams, righto my-cool-lib could ALL be used with async-await.
GOAT TRACKS
This restriction is a major concern for me, as it lays down an iron path that restricts the formation of goat tracks. It enforces the usage of Promises, even though there may be better options out there. It restricts experimentation with alternatives.
I’m personally not a fan of the async-await pattern, as it attempts to hide asynchronicity with synchronous looking code, deterring understanding. If it is going to be used widely, it should be at least given the opportunity to be used in a variety of ways, to see what works best.
I’m anxious of the day we all look back and think “Oh no, that was a huge mistake.”
– Contains opinions.