0

Im pretty new to using the await keyword, I am used to the older "Promise" like commands used in Cypress.

However one confusion I have is when a function returns a promise, but there is both an await keyword IN the function and when using the function. For example:

async goto() {
    await this.page.goto('https://playwright.dev');
  }

Above is a simple method used in Page Object Model pattern, page.goto returns a promise, so we await it.....which makes sense, but to use that you also have to await via:

await playwrightDev.goto();

So I understand the syntax, because the goto function is async. However I guess I don't really understand "why" we have to do this. More specifically why the function has to be async. Because the inner command of the function is already waiting for a promise, why does the function itself need to be async. Since it won't return until the innermost command is done anyways?

Hopefully what I am asking makes sense. I understand the syntax but not WHY it is like this.

msmith1114
  • 2,717
  • 3
  • 33
  • 84
  • Do you mean `why` like `what's the benefit`? What's the benefit of requiring `await`? – TKoL Apr 06 '23 at 13:39
  • Or `What's the benefit of requiring marking a function as Async, as opposed to Javascript being able to tell that the function is Async by checking if await is in the function code`? – TKoL Apr 06 '23 at 13:41
  • A simple way to look at this: as the goto() is dependent on the response of this.page.goto("link") which is asynchronous, we don't know when goto() function will return a value when called ( which makes it async as well! ). Hope this makes any sense :) – Milan Lakhani Apr 06 '23 at 13:42
  • If you're used to using Promise-style explicit callbacks, you're familiar with the fact that once you're in "Promise Land" you're stuck there. The `async/await` facility uses Promise code internally to implement the semantics. If a function contains an `await`, then it has to be `async` because it *is* asynchronous; when the `await` line inside the function is reached, the Promise will propagate out to the calling environment where `goto()` is called. – Pointy Apr 06 '23 at 13:45
  • Also note that, as with explicit Promise coding, you don't *have* to `await` a call to an asynchronous function. There are times when that makes sense, like firing off a call to an asynchronous logging function and you don't need a return value. – Pointy Apr 06 '23 at 13:46
  • Its all JavaScript , nothing related to playwright. – Vishal Aggarwal Apr 07 '23 at 18:54

2 Answers2

3

Why does the function itself need to be async. Since, it won't return until the innermost command is done anyways?

No. The async function does immediately return: it returns a promise for the eventual return value of the body. It executes asynchronously, suspending and later resuming its own execution whenever there is an await, but this does not affect the caller.

The caller just immediately gets a promise. The caller may choose to continue its own execution, e.g. calling other asynchronous functions and passing all the resulting promises into Promise.all, or it may call .then() on the promise, or it may fire-and-forget the promise. Or it may await it to suspend its own execution until the called function is done.

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

A common beginner misconception is that once you await a promise, you can somehow go back to your mainline synchronous code. On the contrary, once you have a promise, you're stuck in asynchronous mode for any other code that depends on that result, regardless of whether the dependent code is asynchronous or not.

That's not to say you can't have runs of synchronous code along the way, only that any functions that consume a promise themselves basically become promises, transitively.

As a rule of thumb, there's 1 await per promise, with the caveat that any callers of asynchronous code that use await now return promises themselves.

You don't have to use async/await inside the method since there's only a single promise at hand. You can return the promise that page.goto returns, passing it up to the caller and essentially stripping off a superfluous promise wrapper:

goto() {
  return this.page.goto('https://playwright.dev');
}

Either way, the caller will need to await the returned promise, unless they don't care when the navigation happens and don't need the resolved value from the promise, which is usually not the case.

To track your promises, keep your promise resolutions together in the same chain(s), occurring sequentially. Every single promise in the chain needs to be awaited or chained with then. As soon as a single promise is not awaited, the chain breaks and the second part of the chain has no way of getting values from or awaiting completion of the first part.

The reason that calling code is "polluted" by promises has to do with the fact that promises are just syntactic sugar for asynchronous callbacks (think setTimeout), and callbacks don't run in mainline code. All synchronous execution ends before any promises run. Then, when the promises resolve later on, execution resumes, running the "callback" (or code after await or in the next .then handler).

async/await just makes it appear like you're doing it all synchronously in a function, but that's just a syntactical trick, flattening out the callbacks.

If you could use await, then resume mainline synchronous code, it would be a non-asynchronous, blocking call that ties up the single Node thread (imagine the Node version of "this page is not responding" that you may see in the browser when too much blocking CPU-bound processing occurs and the event loop doesn't get a chance to run and repaint the screen or handle an interaction).

ggorlen
  • 44,755
  • 7
  • 76
  • 106