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.
2
Answers
No. The
async
function does immediately return: it returns a promise for the eventual return value of the body. It executesasync
hronously, suspending and later resuming its own execution whenever there is anawait
, 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 mayawait
it to suspend its own execution until the called function is done.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 useawait
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 thatpage.goto
returns, passing it up to the caller and essentially stripping off a superfluous promise wrapper: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
await
ed or chained withthen
. As soon as a single promise is notawait
ed, 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 afterawait
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).