如何在顶层使用异步/等待?


182

我一直在浏览async/ await在浏览了几篇文章之后,我决定自己测试一下。但是,我似乎无法绕开为什么这行不通的想法:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

控制台输出以下内容(节点v8.6.0):

>外部:[对象承诺]

>内部:嘿

为什么函数内部的日志消息随后执行?我认为创建async/ 的原因await是为了使用异步任务执行同步执行。

有没有办法可以使用函数内部返回的值而不使用.then()after main()


4
不,只有时间机器才能使异步代码同步。await只是诺言then语法的糖。
Bergi '17

为什么main返回一个值?如果可以,则可能不是入口点,需要由另一个函数(例如异步IIFE)调用。
Estus Flask,

@estus在我测试节点中的东西时,它只是一个快速的函数名称,不一定代表程序的功能main
Felipe

2
仅供参考,async/await是ES2017的一部分,而不是ES7(ES2016)
Felix Kling

Answers:


267

我似乎无法绕开为什么这行不通的想法。

因为main回报承诺;所有async功能都可以。

在顶层,您必须:

  1. 使用async永不拒绝的顶层函数(除非您希望出现“未处理的拒绝”错误),或者

  2. 使用thencatch,或

  3. (即将推出!)使用top级别await的提案,该提案已进入允许在模块中顶级使用的过程的第3阶段await

#1- async永不拒绝的顶级功能

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

注意catch; 您必须处理promise拒绝/异步异常,因为没有其他事情可做;您没有呼叫者将其传递给。如果愿意,可以在通过catch函数(而不是try/ catch语法)调用它的结果上执行此操作:

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

...更加简洁(出于这个原因,我喜欢)。

或者,当然,不处理错误,而只允许“未处理的拒绝”错误。

#2- thencatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

catch如果发生在链中或在你的错误处理程序将调用then处理程序。(请确保您的catch处理程序不会抛出错误,因为没有任何内容可以处理。)

或两个参数都then

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

再次注意,我们正在注册一个拒绝处理程序。但是以这种形式,请确保您then回调都不会引发任何错误,也没有任何内容可处理。

#3顶级await模块

你不能用 await在非模块脚本的顶层使用await,但是顶层建议第3阶段)允许您在模块的顶层使用它。这与使用顶级async函数包装器(上面的#1)相似,因为您不希望顶级代码拒绝(抛出错误),因为这将导致未处理的拒绝错误。因此,除非您要在出现问题时(例如#1)遇到未处理的拒绝,否则您要将代码包装在错误处理程序中:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

请注意,如果执行此操作,则从该模块导入的任何模块都将等到诺言 await得以;当await评估使用顶层的模块时,它基本上会向模块加载器返回一个承诺(就像async函数一样),该加载器会等到该承诺得到解决,然后再评估依赖于该模块的任何模块的主体。


现在将其视为一个承诺可以解释为什么该函数立即返回。我尝试制作一个顶级匿名异步函数,现在得到的结果很有意义
Felipe

2
@Felipe:是的,async/ await是Promise 中的语法糖(糖的好种类:-))。你不只是想着它为返回一个承诺; 它确实做到了。(详细信息。)
TJ Crowder

1
@LukeMcGregor-我在上面显示了两者,并async首先显示了全选项。对于顶级功能,我可以用任何一种方式看到它(主要是因为async版本上有两个缩进级别)。
TJ Crowder

3
@Felipe-顶级await提案已进入第3阶段,现在我已经更新了答案。:-)
TJ Crowder

1
@SurajShrestha-不,但这不是问题。:-)
TJ Crowder

7

顶层await已进入第3阶段,因此您问题的答案如何在顶层使用async / await?就是将await通话添加到main()

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

要不就:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

请记住,它仍然仅在Webpack@v5.0.0-alpha.15中可用。

如果您使用的是TypeScript,它将降落在3.8版中

v8 增加了对模块的支持

它也通过支持杰诺(由贡萨洛- bahamondez评论)。


很酷 我们是否有任何实现节点的路线图
Felipe

不知道,但是很可能我们很快就会看到TypeScript和Babel实现。TypeScript团队有实施第3阶段语言功能的政策,通常在TC39流程中内置Babel插件来测试提案。见github.com/Microsoft/TypeScript/issues/…–
Taro

这是一个在杰诺可太(只是JS,打字稿仍然不支持它github.com/microsoft/TypeScript/issues/25988deno.land 看到deno.news/issues/...
Gonzalo Bahamondez,

SyntaxError:等待仅在异步功能中有效
Sudipta Dhara

4

解决此问题的实际方法是采用不同的方法。

您的目标可能是某种初始化,通常发生在应用程序的顶层。

解决方案是确保应用程序的顶层只有一个JavaScript语句。如果您的应用程序顶部只有一条语句,那么您可以在其他任何地方随意使用async / await(当然要遵循常规语法规则)

换句话说,将整个顶层包装在一个函数中,使其不再是顶层,并且解决了如何在应用程序的顶层运行异步/等待的问题,而您却没有。

这是应用程序的顶层外观:

import {application} from './server'

application();

1
您是正确的,我的目标是初始化。诸如数据库连接,数据提取等之类的东西。在某些情况下,有必要在继续应用程序的其余部分之前获取用户的数据。本质上,您建议application()异步?
费利佩

1
不,我只是说如果您的应用程序的根目录中只有一个JavaScript语句,那么您的问题就不存在了-所示的顶级语句不是异步的。问题在于,不可能在顶层使用异步-您无法等待到实际上在该级别等待-因此,如果顶层只有一个语句,那么您就回避了该问题。现在,您的初始化异步代码位于某些导入的代码中,因此异步可以正常工作,您可以在应用程序启动时初始化所有内容。
杜加尔公爵

1
更正-应用程序是异步功能。
杜加尔公爵

4
我不清楚,抱歉。关键是通常情况下,在顶层,异步函数不会等待...。JavaScript直接进入下一条语句,因此您无法确定初始化代码是否已完成。如果您的应用程序顶部只有一个语句,那就没关系了。
杜加尔公爵

3

在当前答案的基础上提供更多信息:

node.js当前,文件的内容以类似字符串的方式连接起来以形成函数体。

例如,如果您有一个文件test.js

// Amazing test file!
console.log('Test!');

然后node.js将秘密连接一个看起来像这样的函数:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

需要注意的主要事情是,结果函数不是异步函数。所以你不能用这个词await在其内部直接!

但是说您需要使用此文件中的promise,那么有两种可能的方法:

  1. 不要使用 await 在函数内部直接
  2. 不要使用 await

选项1要求我们创建一个新范围(此范围可以是 async,因为我们可以控制它):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

选项2要求我们使用面向对象的Promise API(使用Promise的方式不太漂亮,但功能相同)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

我个人希望,如果可行,默认情况下,node.js会将代码连接成一个async函数。这样可以摆脱这种头痛。



-2

由于main()异步运行,因此它返回一个Promise。您必须在then()方法中获得结果。并且因为then()返回承诺也是如此,所以您必须调用process.exit()以结束程序。

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )

2
错误。一旦所有的承诺都被接受或拒绝,并且主线程中不再运行任何代码,该过程将自行终止。

@Dev:通常,您希望传递不同的值exit()以表示是否发生错误。
9000

@ 9000是,但是这里没有做,因为默认的退出代码为0,所以不需要包含它

@ 9000实际上,错误处理程序可能应该使用process.exit(1)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.