英文:
Multiple Awaits necessary for async function? (Javascript/Playwright)
问题
我对使用await
关键字相对陌生,我习惯于在Cypress中使用类似"Promise"的旧命令。然而,我困惑的一点是,当一个函数返回一个Promise时,在函数内部和使用函数时都有await
关键字。例如:
async goto() {
await this.page.goto('https://playwright.dev');
}
上面是在页面对象模型模式中使用的一个简单方法,page.goto
返回一个Promise,所以我们用await
等待它……这是可以理解的,但是要使用它,你还必须通过以下方式等待:
await playwrightDev.goto();
所以我理解这个语法,因为goto
函数是async
的。然而,我不太明白"为什么"我们必须这样做。更具体地说,为什么这个函数必须是async
的。因为函数内部的命令已经在等待一个Promise,为什么函数本身还需要是异步的呢?因为它不会返回,直到内部最深层的命令完成为止?
希望我的问题表达清楚。我理解这个语法,但不明白为什么是这样的。
英文:
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.
答案1
得分: 3
为什么函数本身需要是
async
。因为它不会返回,直到最内层的命令完成?
不,async
函数会立刻返回:它返回一个用于最终返回体值的 promise。它会异步执行,每当有await
时,都会暂停自己的执行,然后稍后恢复,但这不会影响调用者。
调用者会立即得到一个 promise。调用者可以选择继续自己的执行,例如调用其他异步函数并将所有生成的 promise 传递给 Promise.all
,或者它可以在 promise 上调用.then()
,或者它可以忽略 promise 的返回值。或者它可以使用 await
来暂停自己的执行,直到被调用的函数完成。
英文:
> 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 async
hronously, 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.
答案2
得分: 1
一个常见的初学者误解是,一旦你await
了一个Promise,你就可以在你的主线同步代码中回到某种状态。相反,一旦你有了一个Promise,无论依赖于该结果的代码是否是异步的,你都会陷入异步模式中。
这并不是说你不能在路上有一段同步的代码运行,只是说消耗Promise的任何函数本身基本上就变成了Promise,传递地。
作为一个经验法则,每个Promise对应一个await
,但要注意,调用使用await
的异步代码的任何调用者现在本身也会返回Promise。
你不必在方法内部使用async
/await
,因为这里只有一个Promise可用。你可以返回page.goto
返回的Promise,将其传递给调用者,从而省去一个多余的Promise包装:
goto() {
return this.page.goto('https://playwright.dev');
}
无论哪种方式,调用者都需要await
返回的Promise,除非他们不关心导航何时发生,并且不需要Promise的解析值,通常情况下这并非如此。
要跟踪你的Promise,保持你的Promise解决方案在同一个链中顺序发生。链中的每个Promise都需要被await
或与then
链接。一旦一个Promise不被await
,链就会断开,链的第二部分就无法从第一部分获取值或等待第一部分完成。
调用代码被“污染”的原因与Promise实际上只是异步回调的语法糖有关(想象一下setTimeout
),回调不在主线代码中运行。所有同步执行在任何Promise运行之前结束。然后,当Promise稍后解析时,执行将恢复,运行“回调”(或在await
之后的代码或在下一个.then
处理程序中的代码)。
async
/await
只是让它看起来像你在一个函数中同步地做所有这些,但这只是一个语法上的技巧,将回调扁平化。
如果你能在使用await
后继续主线同步代码,那将是一个非异步的阻塞调用,会占用单个Node线程(想象一下当发生太多阻塞的CPU密集型处理时在浏览器中可能会看到的“此页面未响应”Node版本,事件循环没有机会运行和重绘屏幕或处理交互)。
英文:
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 await
ed or chained with then
. As soon as a single promise is not await
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 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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论