In order to answer this question we should first say what are actually promises and what they do.
What are promises
Promises are a way to write asynchronous code, which means that a function will return some data in a certain time in the future and the code will continue to execute. For example let's say we want to make a burger because we are hungry. So what we do, we can put the meat on the stove, and in parallel with this we can cut the vegetables. When the meat is ready we remove it from the hot plate and put it in the burger, then we put the vegetables and we are ready. Cooking the meat is the Promise and when it is ready we do something with it, this is what we call asynchronous programming.
Usage
Here a simple function that get some data load some data. These are the two ways to do it with the Promise class which has 2 methods .then when the promise has ended and .catch when there is an error.
const makeRequest = () => {
getUserData
.then(user => {
console.log(user);
});
}
const makeRequest = async () => {
console.log(await getUserData());
}
What we see is that using async & await we write less code but in this way when there is 1 function it doesn't matter if you use await or promise class. But what will happen if there are 2 or more functions that depend on each other. For example, first we want to get a user and then we want to get all clubs that a user has joined. Here are the 2 implementations.
const makeRequest = () => {
getUserData
.then(user => {
getClubsForUser(user.id)
.then(clubs => {
console.log(clubs);
}).catch(err => {
console.log(err.message);
})
}).catch(err => {
console.log(err.message);
})
};
const makeRequest = async () => {
try {
let user = await getUserData();
let clubs = await getClubsForUser(user.id);
console.log(clubs);
}
catch(err) {
console.log(err.message);
}
};
When looking at the first example it gives you a headache. It’s easy to get lost in that nesting, braces. You can see in the second example that the code with async & await is more clean and easy to read. And also what we deal with is the callback hell. Async & await is a syntactical sugar for writing asynchronous code. It makes the code look and behave a little more like synchronous code but it remains to be asynchronous.
Conclusion
Some people may say that using async & await can make the asynchronous code less obvious. But C# had this feature for years and people who are familiar with it know it’s worth this minor, temporary inconvenience.
Top comments (21)
Wait, are you sure you know how to use promise?
This is totally same as your second example which writing in Promise
Obviously, nice and clean, Promise is better.
Yeah, honestly the argument "
async/await
is better because I can write messy code using promises" is getting old. I can write really crappy code with any syntactical construct, but that doesn't make them any better or worse.At the end of the day, I think it mostly comes down to style. If you prefer imperative code, you'll like
async/await
better because it's more imperative. If you prefer functional code, you'll like promises better.First of all async await is a syntax sugar for Promises. Doing one vs another has not sense, both are in the same bandwagon. Secondly async/await can be more compared with Haskell do notation. Imperative code is about side effects, mutations. Until you don't do that you cannot say it's imperative code, therefore using async await vs raw promise is more code style
That doesn't make them the exact same, though. Nor does it make them equivalent in all regards. Even if they are meant to achieve the same goal, they are expressed differently. This means that there's got to be some difference between them. So it makes sense to discuss their differences.
And Structs in Elixir can be compared to classes. That doesn't make them Object-Oriented.
Aren't most asynchronous operations side effects? Last time I checked I/O operations were considered side effects, for instance.
There's more to it than simply saying "imperative code is about side effects and mutations". As far as I'm aware, all programming languages deal with side effects in one way or another, however functional they are.
Besides, imperative code also uses, for instance, statements. In order to deal with errors with
async/await
, you use atry/catch
statement. If you want the value of a variable to depend on whether an asynchronous operation is successful, you need to create a variable outside of thetry/catch
statement and then mutate it depending on the result. I'm no expert, but this doesn't sound very functional to me.I'm pretty sure that was my comment's point as well ;-)
Yes, but you have referred that
async/await
is imperative, and Promises are not. They both are imperative, as they are doing side-effects, so there is no difference.This is the only definition of imperative programming which doesn't break at the long term. Imperative programming needs to change state. You can use statements and write purely functional code. Statement are for sure constructs made for potentially side-effectfull programs, but it doesn't mean you can't use them in FP.
This is example of do notation:
Its look and feel sequential. Does it mean it is imperative code? Its truly not. Does it mean it is not functional code, nope it doesn't either. "Do notation" is syntax sugar for chaining monads, the same as async/await is syntax sugar for chaining Promises.
Async/await was firstly introduced in functional language - F#. There is nothing non-functional in the concept itself.
That is true that in JS when we use async-await the code uses likely statements and try/catch, and truly these aren't concepts from functional world, but there is no difference between .catch in Promise chain and try/catch in async/await. Its the same thing but written differently.
Although I understand the sentiment that Promise chaining feels more like declarative programming, in reality both ways change the state and not declare the change like IO Monad in Haskell does, therefore both are imperative.
Spot on, I was about to write exactly this as a comment ... if you write the code not in a "nested" way but in a "chained" way (and with only one catch block, not two) then the resulting code looks surprisingly similar to ... the async await style code!
The way the code was written in the article mimics more the old "callback hell" style with its deep nesting. The point of promises is exactly that you can get rid of that.
You can argue that async/await is still marginally easier to read than promises but the difference is minor.
Where async/await really gets simpler is if you have loops and conditions. But there you need to watch out - a loop with async calls is not the same as Promises.all(), there are situations where you still may want to use the latter!
In other words, aync/await is great but you should still understand Promises as well (which is the foundation that async/await is built on).
I agree that in this case, there's no need for async/await, but whenenver you have logic that needs to happen between the async requests, it makes things so much cleaner.
E.g. I'm writing a Lambda that scrapes a website, then creates an Item to put into a DB, then makes the async db put:
Also agree with @leob though that understanding Promises is still super. You may well need to combine the two as well, e.g.
(similar example given by Wes Bos in his talk on async/await which I highly recommend: youtube.com/watch?v=DwQJ_NPQWWo)
I'm not a async await hater, for me they are isomorphic which can transform to each other. I even like to mix using promise and async await.
The main problem of this article is the examples to judge async await is better than promise is pretty bad one.
Also, why is no one ever bothered by having to add that try/catch block... I don't understand. I hate it.
Yes,
catch
is much more elegant.Can you explain why there must be a try/catch block?
@Manuele J Sarfatti
In a classic promise you have:
If you switch to using await and you do:
but the promise fails (throws an exception), then you have yourself an unhandled exception, and the browser will most probably crash your app.
The equivalent of the classic promise is therefore:
PS: this is pseudo-code off the top of my head and most probably not working, it's just to give an idea
That syntax is a band-aid for the simplest problems. When you start trying to get conditionals or loops then you're fucked, and that happens very often in my universe.
It's worth mentioning the execution within loops and higher order functions (
map()
etc.) how that is being handled by async/await and promises. There is a nice way of handling multiple async calls with promises, especially if they all run at the same time:the above is vastly different to the async/await style loops:
The latter will execute
console.log('Done!');
without waiting for theelement.asyncCall()
calls.The two pieces of code are not equivalent though. In one you are wrapping the array in Promise.all, and in the second you are not. You can do the same thing by awaiting Promise.all using async/await syntax.
All async is, is just syntactic sugar over promises. If your code is not managing promises correctly it doesn't much matter which syntax you are using.
My point was to show that the behaviour is different. I should have probably said "There is a nice way of handling multiple async calls with
Promise.all
, especially if they all run at the same time". Hats off for clarifying it though!I'm sorry, I am not trying to be nitpicky, but the behavior is actually not different at all. In both cases you are mapping an array to an array of Promises, but in one case you've wrapped it in
Promise.all
. I'm not trying to be hostile but just want to clarify it for others reading because this is a common mistake that JavaScript developers make with Promises.In both cases you are mapping to an array of Promises, because
element.asyncCall
returns a Promise. That you are wrapping that in another Promise by making your lambda an async function and awaiting it does nothing other than to just wrap another Promise around it. The behavior is identical, the code is different.EDIT: for example the non-async/await equivalent to your second example is actually roughly the following:
It's hard to disagree with that. Let me clarify this once again because it seems like I didn't explain what I meant quite clearly in either of those comments.
In my previous experience, especially in the beginning I have struggled to grasp the concept of promises, and using the wrapper syntax of async/await was something that I (incorrectly) settled for, instead of taking my time to study promises. That lead me to believe, that writing the second code (async function with awaited call passed into map) will work in the way identical to that described in the first example. It was the mistake that I have made and couldn't get to the bottom of prior to learning the Promises API and the Promise.all() function.
What I meant to say in my original comment was merely the fact, that awaiting asynchronous calls in async lambda (as passed to the map) does not work the same way as wrapping the array of promises in Promise.all() - hence "different behaviour". Yes, it is the same except for the Promise.all() part, which is what I was trying to illustrate from the start.
I'm sure that the example you just gave will further clarify this for any other readers. Thanks again!
Good point re. simply returning a promise being the same here, thanks! I just pasted from a Lambda I was writing where I had a try/catch block and was returning some messages, so I needed the await before and didn't even think about removing it. E.g.:
Re. splitting the function that's a good idea, thanks. This was a copy/paste from a quick and dirty Lambda function for a personal project, but I like how your promise chain rewrite is both easier to read and more testable. Will think twice next time instead of lazily reaching for my async/await 💪
Good article. People just jump at your throat immediately, but all you needed to get your point across was a few lines of code:
Vs
Whoever prefers the second way just likes to write more code, not to mention async/await code is less clunky and easier on the eyes, heck, async/await may even result on smaller minified files.
Nice article and a nice analogy. Yes, I agree with you.. I have worked with Promises and async await is a lot more cleaner way of writing and maintaining code.
Coming from the world of PHP, it feels a lot more natural to me as well :)