17

I’m working on an Angular 6 application and I’ve been told the following is an anti-pattern:

await someFunction().then(result => {
    console.log(result);
});

I realize that it is pointless to await a promise chain. If someFunction() returns a promise, you don’t need a promise chain if you’re awaiting it. You can do this:

const result = await someFunction();
console.log(result);

But I’m being told awaiting a promise chain can cause bugs, or that it will break things in my code. If the first code snippet above does the same thing as the second snippet, what does it matter which one is used. What dangers does the first snippet introduce that the second one doesn’t?

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
Gibran Shah
  • 871
  • 3
  • 15
  • 34
  • 1
    I think a top-level "await someFunction()" is not possible, it has to be wrapped inside some async function, or not? – MMMM Feb 08 '19 at 18:43

3 Answers3

16

I’m being told awaiting a promise chain will break things in my code.

Not necessarily, your two code snippets do indeed work the same (as long as someFunction() really returns a promise).

What does it matter which one is used. What dangers does the first snippet introduce that the second one doesn’t?

It's harder to understand and maintain, it's confusing to mix different styles. Confusion leads to bugs.

Consider that you would need to add another promise call at the location of the console.log() call, or even a conditional return from the function. Can you use await in the callback like elsewhere in the function, do you need to return the result from the then callback, is it even possible to return from the outer function? All these questions don't even come up in the first snippet. And while they can be easily answered for your toy example, it might not be as easy in real code with more complex and nested control flow.

So you should prefer the more concise and clean one. Stick to await for consistency, avoid then in async functions1.

1: Of course, there's always an exception to the rule. I would say that it's cleaner to use promise chaining for error handling in some cases where you would use catch or the second then callback.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
11

Under the hood, async/await is just promises.

That is, when you have some code that looks like:

const result = await myAsyncFunction();   
console.log(result): 

That's exactly the same as writing:

myAsyncFunction().then(data => {
   const result = data; 
   console.log(result); 
}); 

The reason then - that you shouldn't mix async/await and .then chains - is because it's confusing.

It's better to just pick one style, and stick to it.

And while you're picking one - you might as well pick async/await - it's more understandable.

dwjohnston
  • 11,163
  • 32
  • 99
  • 194
  • 5
    `it's more understandable` that's 100% opinion. I've found it difficult to explain to some people that an `async` function actually returns to the caller at the first `await` expression. Promises alone have less of a learning curve than `async` / `await`, in my opinion. – Patrick Roberts Jan 27 '19 at 06:53
  • 1
    actually, I think that first code example above is wrong, because there is no top-level await call.. It should be wrapped inside some async function() block, otherwise the awai can not be executed, or am I wrong? Thats the disadvantage of async-await in my opinion... its a bit overhead imho.. – MMMM Feb 08 '19 at 18:42
-1

If your then code returned a promise instead of calling console.log, your first example would await, but your second example would not.

When you use async/await, you will catch your rejects in try/catch blocks. Your code will be less nested and clearer.

Using then often results in more nesting, and harder to read code.

You can await anything, whether or not it returns a promise. Sometimes this future-proofs calling a method that may one day become async or just return a promise without declaring async.

The downsides are complexity, performance, and compatibility, all of which pale in comparison to the gains.

I find that if you rely on a function's return value after calling it, and it is or may eventually become asynchronous, decorate calling your functions with await to your heart's delight, whether or not it is current async or returns a promise.

Bryan
  • 2,870
  • 24
  • 39
  • 44
Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
  • 2
    For explaining a scenario where this introduces a bug, +1. For concluding with bad (can't tell if sarcastic?) advice, -1. – Patrick Roberts Jan 27 '19 at 06:56
  • I don't see how any of this is sarcastic. Are you one of those `async/await` haters? I have taught hundreds of student to use them, and then NEVER go back. – Steven Spungin Jan 27 '19 at 07:06
  • 2
    `So decorate your code with await to your heart's delight!` seems very much like it's implying to use it when it's unnecessary, especially when coupled with your previous assertion that you can `await` anything. So, should I `var foo = await "bar";` just because it's valid? Maybe someday I'll change that string to a promise? Meanwhile I have to put it in an `async` function just because I felt like sticking a random `await` expression to "future-proof" something. Yes I'm being ridiculous, but it still sounds like sarcasm to me when I read the statement at the end of your answer. – Patrick Roberts Jan 27 '19 at 07:11
  • I suggest you look up the definition or sarcasm. In the meantime I will clarify to await calling functions, although that is really self explanatory. If you call foo() and it is not async, but may be some day, there is NO HARM to awaiting it. – Steven Spungin Jan 27 '19 at 07:16
  • 1
    Yes there is. If the function returns an object that defines a `then()` function, `await`ing it is _completely_ different than `return`ing it. That is indeed harmful, and not compatible behavior. – Patrick Roberts Jan 27 '19 at 07:18
  • Yor last point was the first line stated in my solution, and exactly why using `await` generously has an advantage. – Steven Spungin Jan 27 '19 at 07:24
  • 3
    "*foo() is sync but may some day become async*" - that's no good reason to use `await`, treating it as if it was async today already. Whether a function is asynchronous or not is an important part of its API contract, and must not change offhandedly. Document what you are doing and keep it at that. – Bergi Jan 27 '19 at 11:32
  • @bergi I disagree 100% and am going to leave it at that. – Steven Spungin Jan 27 '19 at 12:47