在 Async/Await 下留下无用的同步代码是一种反模式吗?
Is leaving useless synchronous code under Async/Await an anti-pattern?
根据我的理解,await
背后的要点是 'await' 对承诺的已解决值采取行动,直到它作为微任务遇到,正如 Jake Archibald 解释的那样 here .
This video by LLJS 表明 async-await 本质上是生成器 runner/interpreter 函数的语法糖,它在等待的地方产生并将 promise 的确定值传递给 .next()
方法。这意味着运行器执行的 .next()
在等待发生时作为微任务排队。
实际上,await
下的所有代码只会在下一个微任务检查点执行。如果不需要承诺的等待值的代码位于它下面,这可能是一个问题,这正是异步 IIFE 的问题。
async function ping() {
for (let i = 0; i < 5; i++) {
let result = await Promise.resolve("ping");
console.log(result);
}
console.log("Why am I even here?");
}
async function pong() {
for (let i = 0; i < 5; i++) {
let result = await Promise.resolve("pong");
console.log(result);
}
console.log("I have nothing to do with any of this");
}
console.log("Let the games begin!");
ping();
pong();
console.log("Placeholder for code that is not related to ping pong");
在此示例中,外部日志首先作为 运行 脚本任务的一部分记录,然后按照它们在微任务队列中排队的顺序记录已解决承诺的值。在这整个过程中,留在 for 循环下面的日志与循环无关,并且被不必要地暂停,直到它们各自函数体中的最后一个微任务离开队列。
这正是我们将 async
函数用作 IIFE 时发生的情况。如果 await
下的代码是要同步执行的,则它必须不必要地等待,直到它上面的所有等待都已从微任务队列中检出。
如果有人盲目地将他们的整个快速路线包装在 async
函数中,我可以看到这是一个问题,在那里他们会不必要地 await
解决某些承诺,如数据库操作、发送电子邮件, 读取文件等等..., 那为什么还有人这么做呢?
app.post('/forgotPwd', async (req, res) => {
const {email, username} = req.body;
if (!email) {
res.status(400).json({error: "No username entered"});
return;
}
if (!username) {
res.status(400).json({error: "No email entered"});
return;
}
const db = client.db();
const user = await db.collection("Users").findOne({username: username, "userInfo.email": email});
if (!user) {
res.status(400).json({error: "Account not found"});
return;
}
const authToken = await getAuthToken({id: user._id.toHexString()}, "15m");
// Would probably send a more verbose email
await sgMail.send({
from: process.env.EMAIL,
to: email,
subject: 'Forgot Password',
text: `Use this url to reset your password: http://localhost:5000/confirmation/passConf/${authToken}`,
});
res.json({error: ""});
});
如果您希望 async
函数中的某些内容同步 运行,请确保它在函数中的第一个 await
之前。
So why do people still do this?
对于 SO 这可能是一个题外话的问题,因为它主要要求基于意见的答案,但它可能是 A) 因为他们不 想要那个代码到 运行 直到上面的代码完成,或者 B) 因为他们不理解 async
函数。
使用 async/await 的目的是让异步代码看起来是同步的,因为它更容易阅读。事实上,它 是 一个隐藏回调地狱的语法糖。您无需处理回调即可处理异步操作。
在你的例子中,如果你认为 for
循环之后的代码与等待的操作无关,你不应该把它放在 await
之后。或者您应该重构代码,使其不使用 await
(回调)。
至于人们为什么这样做的问题。那么,您能说说为什么人们使用 .map()
来代替 .forEach()
吗?或者你能说出他们为什么不处理异常吗?他们可能不完全理解它,或者(如 T.J. Crowder 提到的那样)他们确实希望代码在等待的操作后变为 运行。就这么简单。
根据我的理解,await
背后的要点是 'await' 对承诺的已解决值采取行动,直到它作为微任务遇到,正如 Jake Archibald 解释的那样 here .
This video by LLJS 表明 async-await 本质上是生成器 runner/interpreter 函数的语法糖,它在等待的地方产生并将 promise 的确定值传递给 .next()
方法。这意味着运行器执行的 .next()
在等待发生时作为微任务排队。
实际上,await
下的所有代码只会在下一个微任务检查点执行。如果不需要承诺的等待值的代码位于它下面,这可能是一个问题,这正是异步 IIFE 的问题。
async function ping() {
for (let i = 0; i < 5; i++) {
let result = await Promise.resolve("ping");
console.log(result);
}
console.log("Why am I even here?");
}
async function pong() {
for (let i = 0; i < 5; i++) {
let result = await Promise.resolve("pong");
console.log(result);
}
console.log("I have nothing to do with any of this");
}
console.log("Let the games begin!");
ping();
pong();
console.log("Placeholder for code that is not related to ping pong");
在此示例中,外部日志首先作为 运行 脚本任务的一部分记录,然后按照它们在微任务队列中排队的顺序记录已解决承诺的值。在这整个过程中,留在 for 循环下面的日志与循环无关,并且被不必要地暂停,直到它们各自函数体中的最后一个微任务离开队列。
这正是我们将 async
函数用作 IIFE 时发生的情况。如果 await
下的代码是要同步执行的,则它必须不必要地等待,直到它上面的所有等待都已从微任务队列中检出。
如果有人盲目地将他们的整个快速路线包装在 async
函数中,我可以看到这是一个问题,在那里他们会不必要地 await
解决某些承诺,如数据库操作、发送电子邮件, 读取文件等等..., 那为什么还有人这么做呢?
app.post('/forgotPwd', async (req, res) => {
const {email, username} = req.body;
if (!email) {
res.status(400).json({error: "No username entered"});
return;
}
if (!username) {
res.status(400).json({error: "No email entered"});
return;
}
const db = client.db();
const user = await db.collection("Users").findOne({username: username, "userInfo.email": email});
if (!user) {
res.status(400).json({error: "Account not found"});
return;
}
const authToken = await getAuthToken({id: user._id.toHexString()}, "15m");
// Would probably send a more verbose email
await sgMail.send({
from: process.env.EMAIL,
to: email,
subject: 'Forgot Password',
text: `Use this url to reset your password: http://localhost:5000/confirmation/passConf/${authToken}`,
});
res.json({error: ""});
});
如果您希望 async
函数中的某些内容同步 运行,请确保它在函数中的第一个 await
之前。
So why do people still do this?
对于 SO 这可能是一个题外话的问题,因为它主要要求基于意见的答案,但它可能是 A) 因为他们不 想要那个代码到 运行 直到上面的代码完成,或者 B) 因为他们不理解 async
函数。
使用 async/await 的目的是让异步代码看起来是同步的,因为它更容易阅读。事实上,它 是 一个隐藏回调地狱的语法糖。您无需处理回调即可处理异步操作。
在你的例子中,如果你认为 for
循环之后的代码与等待的操作无关,你不应该把它放在 await
之后。或者您应该重构代码,使其不使用 await
(回调)。
至于人们为什么这样做的问题。那么,您能说说为什么人们使用 .map()
来代替 .forEach()
吗?或者你能说出他们为什么不处理异常吗?他们可能不完全理解它,或者(如 T.J. Crowder 提到的那样)他们确实希望代码在等待的操作后变为 运行。就这么简单。