Home > Net >  Why doesn't .then() need the async keyword when used (similar to await)? How does Javascript kn
Why doesn't .then() need the async keyword when used (similar to await)? How does Javascript kn

Time:10-29

I'm starting to learn asynchronous Javascript and I'm really confused.

To be honest, the async/await approach seems very logical to me. We need to let the runtime know we're doing an asynchronous operation so it can handle it accordingly. But why don't we need to do the same when using the .then() method? I mean, if Javascript was already able to understand when promises are handled, couldn't await just be used without async just like .then()?

To make it even more confusing, I saw people using .then() directly inside functions declared with the async keyword. Wasn't async/await supposed to be syntactic sugar for the .then().catch() approach? Why can these be combined, especially inside one another? Using .then() on the result of the async function wouldn't have been as confusing, but being inside one another makes me have even a harder time understanding this.

I really searched everywhere for an explanation on this and couldn't find an answer for this exact question. All I've found was people saying you can use both approaches because they essentially are the same thing, but when you get in the details, things aren't very clear.

So the async function always returns a promise. Inside it, await always handles promises. .then() can be chained to the await function. .then() can also be chained to the result of the async function. Same with the .catch method if we don't want to use try/catch on the await. Why is it so mixed up? Can we handle the return of async without .then()? If async/await really is syntactic sugar for .then(), why doesn't .then() also always return a promise after it resolves?

If anybody can help with some clarification, I would truly appreciate it. Thank you!

CodePudding user response:

There's so many questions here, but one way to think about it is... Promises are not asynchronous operations, they are just a standard pattern that helps users deal with existing asynchronous operations in a predicable way.

There's nothing special about .then(). It's not a Javascript keyword, and javascript doesn't need to be 'aware' of then in any way.

Promises are a pattern. You can write a promise class yourself from scratch (I'd highly recommend this). When you use .then(), and you pass it a function, you are telling the promise class: "When the promise resolves, please call this function for me".

All of this existed before async/await. Async/await was added after to make it easier to work with promises.

I find the question if 'something is syntactic sugar' meaningless. It suggests that a language feature is not that meaningful because the same thing could be accomplished before the language feature existed. While this might be true, this is true for any programming language when compared to, say, assembly. To me the definition of 'syntax sugar' can be extended to almost anything, so it's not a useful designation.

What async/await (and generators before it) adds to the language, is that a javascript function can be interrupted and resumed later after some condition.

Lastly, .then() and .catch() always return promises. If you see a .then() function that doesn't, then it's not compatible with the Promise/A spec.

If you are willing to put in the work to fully understand this, I would recommend the following 2 exercises:

  • Write your own Promise class
  • Re-implement async/await using generator functions (see the co package of how this was done before async/await landed)

CodePudding user response:

The purpose of async/await is to allow writing async code in a serial manner, which is mentally simpler to reason about (for some human-beings). This is useful, if you need to wait for async operation to finish before you continue with the rest of the code. For example, if you need to pass result of async operation as parameter.

Example 1

function asyncOperation1(n) { return Promise.resolve(n 1); }
function asyncOperation2(n) { return Promise.resolve(n/2); }
function asyncOperation3(n) { return Promise.resolve(n*3); }
function errorHandler(err) { console.error(err); }

function main() {
  // flow-control
  asyncOperation1(1)
    .then(asyncOperation2)
    .then(asyncOperation3)
    .then(continueAfterAsync)
    .catch(errorHandler)

  // function wrapper
  function continueAfterAsync(result) {
    console.log(result);
  }
}

main();

With async/await the code of the main function above may look like

async main() {
  try {
    console.log(
      await asyncOperation3(
        await asyncOperation2(
          await asyncOperation1(1)
        )
      )
    );
  } catch(err) {
    errorHandler(err);
  }
}

Pay attention that we don't need to rewrite async operation functions to be async function asyncOperation... to use await, but we need to declare main function as async main.

Which one is better(?) is the mater of developers's taste and previous programming languages experience. The benefit that I can see is that you don't need to wrap everything into functions and introduce additional flow-control code, leaving this complexity to JavaScript compiler.

However, there are cases, when you want to schedule some parallel tasks and you don't care which one will finish first. These kind of things would be relatively hard to do with async/await only.

Example 2

function main() {
  Promise
    .all(
      ['srv1', 'srv2', 'srv3'].map(
        srv => fetch(`${srv}.test.com/status`)
      )
    ])
    .then(
      responses => responses.some(res => res.status !== 200) ?
        console.error('some servers have problems') :
        console.log('everything is fine')
    )
    .catch(err => console.error('some servers are not reachable', err))
}

So, we see that there is a room for both .then() and await to coexist.

In some cases function may be either synchronous or asynchronous, depending on business logic (I know it's ugly, but in some cases it's unavoidable). And here we come to your main question

why don't we need to mark an asynchronous operation with .then() and we have to do it with await

In other words, why do we need async keyword at all?

Example 3

// without `async`
function checkStatus(srv) {
  if (!srv.startsWith('srv')) {
    throw new Error('An argument passed to checkStatus should start with "srv"')
  }
  return fetch(`https://${srv}.test.com/status`);
}

function main() {
  // this code will print message
  checkStatus('srv1')
    .then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
    .catch(err => console.error(err));

  // this code will fail with
  // Uncaught TypeError: (intermediate value).then is not a function
  checkStatus('svr1')
    .then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
    .catch(err => console.error(err));
}

However, if we define async function checkStatus, compiler will wrap the runtime error into rejected promise return value, and both parts of the main function will work.

Now let's imagine that JavaScript allows to write functions that use await without specifying async in front of them.

Example 4 (not a valid Javascript)

function checkStatus(srv) {
  if (cache[srv]) {
    data = cache[srv];
  } else {
    data = (await fetch(`https://${srv}.test.com/status`)).json();
  }
  data.x.y = 'y';
  return data;
}

What would you expect checkStatus to return? Promise, raw value or throw exception (in case data.x is undefined)?

If you say Promise, then it would be hard for developer that uses this function to understand why inside of checkStatus one can write data.x and outside of it (await data).x is required.

If raw value, the whole execution flow becomes cumbersome, and you can no longer rely on the fact that JavaScript is a single-threaded language, where no-one can change the value of the variable between two lines of code that are written in serial manner.

As you noticed, async/await is a syntactic sugar. If this syntax allows me to avoid possible runtime errors at earlier stage and keep the language backward compatible, I'm eager to pay the price of putting extra async in front of async functions.

Also, I would recommend to read the answers to JS async/await - why does await need async?

  • Related