为什么在JS中需要将函数定义为async?

huangapple go评论69阅读模式
英文:

Syntax question: Why is it necessary to define a function as async in JS?

问题

这只是一个简单的问题。我们有await关键字,允许函数在继续执行之前返回结果。当然,你也可以以相同的方式使用Promises,但我在这个问题中特别关注async。

在以下上下文中,async函数会出现:

const funcA = async newValue => {
    await connection.query(`UPDATE t SET field = ?`, [newValue])
    .catch(error => console.log(error));

    const valueA = await connection.query(`SELECT t.field FROM t LIMIT 1`)
    .then(([results, fields]) => (results?.[0] ?? null))
    .catch(error => console.log(error));

    console.log(valueA.field);
};

funcA("test");

不使用async的执行类似方法如下:

const funcA = newValue => {
    connection.query(`UPDATE t SET field = ?`, [newValue])
    .then(() => connection.query(`SELECT t.field FROM t LIMIT 1`))
    .then(([results, fields]) => (results?.[0] ?? null));
    .then(valueA => console.log(valueA.field))
    .catch(error => console.log(error));
};

funcA("test");

现在忽略这个示例代码的实用性,只关注功能,为什么我们需要在定义函数之前使用async关键字呢?为什么不允许在每个函数中使用await。第二个示例可以加上async关键字,但它不会有任何不同,因为Promises在两个上下文中的工作方式相同。我们是否可以完全消除async关键字呢?

我相信我肯定是在这里漏掉了什么,但我不知道背后的原因。

英文:

It's a simple question. We have the await keyword which allows the function execution to return a result before continuing. Of course you can use promises in the same way, but I'm specifically interested in async in this question.

An async function would appear in the following context.

const funcA = async newValue => {
    await connection.query(`UPDATE t SET field = ?`, [newValue])
    .catch(error => console.log(error));

    const valueA = await connection.query(`SELECT t.field FROM t LIMIT 1`)
    .then(([results, fields]) => (results?.[0] ?? null))
    .catch(error => console.log(error));

    console.log(valueA.field);
};

funcA("test");

A similar method of executing this without async would be

const funcA = newValue => {
    connection.query(`UPDATE t SET field = ?`, [newValue])
    .then(() => connection.query(`SELECT t.field FROM t LIMIT 1`))
    .then(([results, fields]) => (results?.[0] ?? null));
    .then(valueA => console.log(valueA.field))
    .catch(error => console.log(error));
};

funcA("test");

Now ignoring the practicality of this example code, and just focusing on the functionality, why do we need to use the async keyword before defining the function at all? Why not allow await in every function. The second example could have the async keyword but it wouldn't work any differently because promises work the same in both contexts. Couldn't we just eliminate the async keyword entirely?

I'm certain I must be missing something here but I don't know the reasoning behind it.

答案1

得分: 1

好的,这应该不被视为权威答案,更像是一种民间传统回答。

当代码调用函数时,就像我们的祖父母调用函数:

let x = f();

函数被调用,它执行它的任务,并将其返回值赋给 x。

现在让我们谈谈 async 函数。这样的函数旨在帮助处理异步操作调用的顺序问题。以前,这需要构建一系列的 .then()、回调和 .catch() 来实现。那样能够工作,但容易令人困惑且容易出错。

asyncawait 提供的是处理这种 Promise 操作序列的抽象。在底层,Promise 机制仍然做着同样的事情。一个可以考虑的方法(并非完全准确)是,await 操作符将其后的代码转化为一个 .then() 回调函数。

因为单个 async 函数可能在内部有许多 await 调用,当在某个上下文中使用 await 调用它时,我们必须处理单个调用可能返回一个接着一个的 Promise 的情况:

const af = async function() {
    let val = await a();
    val = await b(val);
    val = await c(val);
    return val;
}

async 函数内部的每个 await 都会返回一个 Promise。但考虑调用 af

let foo = await af();

为了评估它,函数内部 await 表达式返回的三个 Promise 对象必须依次完成,然后调用上下文中的代码才能继续执行后续语句。

好的,现在,如果你还没有慌乱,你可以 await 任何你想要的东西:

let d = await Date.now();

这样也可以正常工作。d 的值将是一个 Promise,因为如果等待的表达式不是 Promise,实际上你会得到:

let d = Promise.resolve().then(_ => Date.now());

当然 d 仍然是一个 Promise,这有点混乱。

另外,你可以调用一个 async 函数而不使用 await,这也没问题,只要你意识到你会得到一个 Promise。

因为这些东西很难用精确的机械细节来解释,对我来说最好养成坚持使用 asyncawait 并遵循规则的习惯。除非你真的需要,不要混合 .then() 的代码进去。容易忘记返回 Promise .then() 链的结果。使用 asyncawait,你只需要关心你调用的函数,它们要么是明确地 async,要么是你知道会返回 Promise 对象的函数(因为在这种情况下 await 也能正常工作)。

英文:

OK this should not be taken as an authoritative answer. More like a folk tradition answer.

When code calls a function the way our grandparents called functions:

let x = f();

the function is invoked, it does what it does, and its return value is assigned to x.

Now let's talk about an async function. Such functions are intended to help deal with the problem of sequencing calls to asynchronous operations. Formerly, that required constructing chains of .then() and callbacks, and .catch() for exceptions. That works, but it's confusing and error-prone.

What async and await provide is an abstraction around handling that kind of Promise sequence of operations. Underneath, the Promise mechanism still does the same thing. A way to think about it (that is not entirely accurate) is that the await operator turns the code that follows it into a .then() callback for you.

Because a single async function may have many await calls internally, when it is called with await from some context, there we have to handle the fact that the single call may return Promise after Promise:

const af = async function() {
    let val = await a();
    val = await b(val);
    val = await c(val);
    return val;
}

Each await inside the async function will return a Promise. But consider calling af:

    let foo = await af();

To evaluate that, the three Promise objects returned from the await expressions within the function must all complete, one after the other, before the code in the calling context can move on to the subsequent statements.

OK, now, if your hair is not on fire yet, it is possible to await anything you want:

let d = await Date.now();

That works fine. The value of d will be a Promise, because if the expression that's awaited isn't a Promise you effectively get

let d = Promise.resolve().then(_ => Date.now());

Of course d will still be a Promise, and that's kind-of a mess.

Also, you can call an async function without await and that's fine, as long as you realize that you're getting a Promise back.

Because this stuff is really hard to explain in precise mechanical detail, to me it's better to be in the habit of sticking with async and await and following the rules. Don't mix .then() code into the mix unless you really need to. It's easy to forget to return the result of a Promise .then() chain. With async and await, all you have to worry about is keeping track of the functions you're calling that are either explicitly async or that you know will return Promise objects (because await will work fine in that case too).

huangapple
  • 本文由 发表于 2023年7月6日 11:04:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76625226.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定