如何在顶层使用 async/await?

How can I use async/await at the top level?

我一直在研究 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);

控制台输出如下(node v8.6.0):

> outside: [object Promise]

> inside: Hey there

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

有没有一种方法可以在 main() 之后不使用 .then() 而使用函数内部返回的值?

I can't seem to wrap my head around why this does not work.

因为mainreturns一个承诺;所有 async 函数都可以。

在顶层,您必须:

  1. 在现代环境中使用 top-level await (ES2022, broadly supported)允许在模块中使用 await 的顶层。

  2. 使用从不拒绝的顶级 async 函数(除非您想要“未处理的拒绝”错误)。

  3. 使用thencatch

模块中的第 1 个顶层 await

您可以在模块的顶层使用 await (details)。在您 await 达成承诺之前,您的模块不会完成加载(这意味着等待您的模块加载的任何模块在承诺达成之前不会完成加载)。如果承诺被拒绝,您的模块将无法加载。通常,顶层 await 用于您的模块在承诺得到解决之前无法完成其工作并且除非承诺得到履行否则根本无法完成的情况,所以这就是罚款:

const text = await main();
console.log(text);

如果即使 promise 被拒绝,您的模块仍能继续工作,您可以将顶层 await 包装在 try/catch:

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

当使用顶级 await 的模块被评估时,它 returns 对模块加载器的承诺(就像 async 函数所做的那样),它会等待直到该承诺被执行在评估依赖它的任何模块的主体之前解决。

您不能在非模块脚本的顶层使用 await,只能在模块中使用。

#2 - 从不拒绝的顶级 async 函数

(async () => {
    try {
        const text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
    // `text` is not available here
})();
// `text` is not available here, either, and code here is reached before the promise settles
// and before the code after `await` in the main function above runs

注意catch;你必须 处理 promise 拒绝/异步异常,因为没有其他事情要做;你没有调用者将它们传递给(与上面的#1 不同,你的“调用者”是模块加载器)。如果您愿意,可以根据通过 catch 函数(而不是 try/catch 语法)调用它的结果来执行此操作:

(async () => {
    const text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});
// `text` is not available here, and code here is reached before the promise settles
// and before the code after `await` in the main function above runs

...虽然它有点混合了模型(async/await 和显式承诺回调)……但我通常建议不要这样做。

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

#3 - thencatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });
// `text` is not available here, and code here is reached before the promise settles
// and the handlers above run

如果链中或您的 then 处理程序中出现错误,将调用 catch 处理程序。 (确保您的 catch 处理程序不会抛出错误,因为没有注册任何内容来处理它们。)

then 的两个参数:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);
// `text` is not available here, and code here is reached before the promise settles
// and the handlers above run

再次注意我们正在注册拒绝处理程序。但是在这种形式中,请确保 中的 then 回调抛出任何错误,因为没有注册任何东西来处理它们。

因为 main() 异步运行它 returns 一个承诺。您必须在 then() 方法中获得结果。而且因为 then() returns promise 也是,你必须调用 process.exit() 来结束程序。

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

这个问题的实际解决方案是采用不同的方法。

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

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

换句话说,将整个顶层包装在一个函数中,使其不再是顶层,这样就解决了如何 运行 async/await 处于顶层的问题应用程序 - 你不需要。

您的应用程序的顶层应如下所示:

import {application} from './server'

application();

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

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

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

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

然后 node.js 将秘密连接一个函数,如下所示:

function(require, __dirname, ... perhaps 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!'));

看到节点添加对顶级的支持会很有趣 await!

Top-Level await 已进入第 3 阶段,因此您的问题 如何在顶层使用 async/await? 的答案是仅使用await:

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

Of 如果你想要一个 main() 函数:将 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)

兼容性

您现在可以在 Node v13.3.0

中使用顶级等待
import axios from "axios";

const { data } = await axios.get("https://api.namefake.com/");
console.log(data);

运行 它带有 --harmony-top-level-await 标志

node --harmony-top-level-await index.js

节点-
在 REPL 中你可以 运行 node --experimental-repl-await。我不太确定脚本。

Deno -
Deno 已经内置了它。

我喜欢这种从入口点进行异步工作的巧妙语法

void async function main() {
  await doSomeWork()
  await doMoreWork()
}()

其他解决方案缺少 POSIX 合规性的一些重要细节:

你需要...

  • 成功时报告 0 退出状态,失败时报告非零退出状态。
  • 将错误发送到 stderr 输出流。
#!/usr/bin/env node

async function main() {
 // ... await stuff ... 
}

// POSIX compliant apps should report an exit status
main()
    .then(() => {
        process.exit(0);
    })
    .catch(err => {
        console.error(err); // Writes to stderr
        process.exit(1);
    });

如果您使用的是 commander 之类的命令行解析器,则可能不需要 main()

示例:

#!/usr/bin/env node

import commander from 'commander'

const program = new commander.Command();

program
  .version("0.0.1")
  .command("some-cmd")
  .arguments("<my-arg1>")
  .action(async (arg1: string) => {
    // run some async action
  });

program.parseAsync(process.argv)
  .then(() => {
    process.exit(0)
  })
  .catch(err => {
    console.error(err.message || err);
    if (err.stack) console.error(err.stack);
    process.exit(1);
  });

在 NodeJS 14.8+ 中,您可以使用顶级 await 模块(#3 解决方案)。您也可以将 .js 重命名为 .mjs(ES 模块)而不是 .js(.cjs CommonJS)。

2021 答案:您现在可以在当前稳定版本的节点中使用顶级等待

上面的大部分答案都有些过时或非常冗长,所以这里有一个针对节点 14 及更高版本的快速示例。

创建一个名为 runme.mjs 的文件:

import * as util from "util";
import { exec as lameExec } from "child_process";
const exec = util.promisify(lameExec);
const log = console.log.bind(console);

// Top level await works now
const { stdout, stderr } = await exec("ls -la");
log("Output:\n", stdout);
log("\n\nErrors:\n", stderr);

运行 node runme.mjs

Output:
 total 20
drwxr-xr-x  2 mike mike 4096 Aug 12 12:05 .
drwxr-xr-x 30 mike mike 4096 Aug 12 11:05 ..
-rw-r--r--  1 mike mike  130 Aug 12 12:01 file.json
-rw-r--r--  1 mike mike  770 Aug 12 12:12 runme.mjs



Errors:
  1. 您需要在package.json

    中添加类型
    "type": "module"
    
  2. 一切顺利。

    import axios from 'axios';
    const res = await axios.get('https://api.github.com/users/wesbos');
    console.log(res.data);
    

请记住,如果您更改文档类型,那么您必须以 ES6 方式编写代码。

现在使用 ECMAScript22,我们可以在 top-level 模块中使用 await

这是一个例子 ( await top-level ):

const response = await fetch("...");
console.log(response):

另一个例子没有 (await top-level )

  async function callApi() {
    const response = await fetch("...");
    console.log(response)      
}
callApi()