JS异步/等待 - 为什么等待需要异步?

21

为什么使用await需要将外部函数声明为async

例如,为什么这个mongoose语句需要它所在的函数返回一个promise?

async function middleware(hostname, done) {
  try {
    let team = await Teams.findOne({ hostnames: hostname.toLowerCase() }).exec();
    done(null, team);
  } catch (err) { done(err); }
}

我看到运行时/转译器将团队承诺解析为其值并异步信号"抛出"拒绝的承诺。

但是try/catch“捕获”那些被拒绝的承诺,那么为什么async和await紧密耦合呢?


我不明白你所说的“但是try/catch会‘捕获’那些被拒绝的Promise”是什么意思。这与async关键字有什么关系? - Bergi
可能是为什么需要使用async关键字的重复问题。 - Bergi
这个mongoose语句为什么需要所在的函数返回一个promise?否则它怎样能够等待mongoose异步返回结果呢? - Bergi
这是语言设计的原因:https://dev59.com/yFwZ5IYBdhLWcg3waft6#41744179 - Michael Cole
它应该像C#一样强制实施“全程异步”,但是它只是半成品地被加入到JS中:( ...在JS中实现的方式最终只是一些文本,程序员们学会了必须粘贴才能使其工作。 - kofifus
3个回答

9

我不了解JavaScript语言设计的讨论,但我认为它的原因与the C# language requires async(另请参见my blog)相同。

即:

1. 向后兼容性。如果`await`突然成为一个关键词,那么任何使用`await`作为变量名的现有代码都将中断。由于`await`是一种上下文关键字(通过`async`激活),只有打算使用`await`作为关键字的代码才会使`await`成为关键字。 2. 更易解析。`async`使异步代码更易于被转换器、浏览器、工具和人类解析。

1
我认为这些论点都不能解释为什么使用await需要将函数设置为异步。“易于解析”从来不是语言设计的好理由。它必须更容易学习和编程,解析器是一个程序,应该承担重活。通过使用任何关键字来防止遗留的await符号停止工作而不改变函数从同步到异步可以修复向后兼容性问题。 - Petruza
1
没有真正需要使用异步的原因。 您仍然可以使用同步函数,并通过循环等待 Promise 解决来实现自己的 _await_,从而使父函数和被调用的函数都变成同步的。 我在其他答案中读到的原因是为了避免阻塞主事件循环,但您同步执行的所有操作都会这样做,那么有什么好处呢? - Petruza
1
@Petruza:目标是更易于维护的代码。就可维护性而言,await > then > 回调函数。 - Stephen Cleary
1
@Petruza:在大多数环境中,异步比同步更好,因为它释放了主线程。在浏览器中,同步代码会冻结用户界面。在服务器上,同步代码会阻止其他请求的处理。如果您正在编写命令行进程/脚本,则同步代码是可以接受的,而异步或同步则成为个人偏好的问题。即使在这种环境下,异步也允许并发,如果需要的话。 - Stephen Cleary
所以,如果我调用 await func();,实际上是在异步运行,只是调用范围处于“休眠”状态,直到 Promise 被解决? - Petruza
显示剩余9条评论

7

从@phaux的https://dev59.com/yFwZ5IYBdhLWcg3waft6#41744179复制:

These answers all give valid arguments for why the async keyword is a good thing, but none of them actually mentions the real reason why it had to be added to the spec.

The reason is that this was a valid JS pre-ES7

function await(x) {
  return 'awaiting ' + x
}

function foo() {
  return(await(42))
}

According to your logic, would foo() return Promise{42} or "awaiting 42"? (returning a Promise would break backward compatibility)

So the answer is: await is a regular identifier and it's only treated as a keyword inside async functions, so they have to be marked in some way.

Fun fact: the original spec proposed more lightweight function^ foo() {} for async syntax.


但是使用await不需要空格吗?我的意思是,如果await只是一个变量,那么像var a = await foo()这样的代码是否有效? - user4052054
为什么foo()会返回'awaiting 42'呢?显然,这是由构造决定的。如果没有实际的等待,我猜想return await(somePromise)会在返回解析之前等待某个Promise被解决。或者至少看起来像是一个有用的简写。但实际上,这样的延迟返回基本上就是一个Promise。好的,明白了。 - Samantha Atkins
1
@user4052054 是的,但是 await (expressionThatEvaluatesToAPromise) 是一种有效的语法结构来等待一个Promise,而 await (5) 则是将参数 5 传递给用户定义的函数 await 的有效方法。 - philraj

1

middleware函数中使用await意味着middleware函数不能立即返回结果(必须等待await完成),并且middleware函数的调用者必须等待从middleware函数返回的Promise被解决。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接