ChatGPT解决这个技术问题 Extra ChatGPT

如何在顶层使用 async/await?

我一直在浏览 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):

> 外部:[object Promise] > 内部:嘿那里

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

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

不,只有时间机器才能使异步代码同步。 await 只不过是 promise then 语法的糖。
为什么 main 会返回一个值?如果应该,可能它不是入口点,需要由另一个函数(例如异步 IIFE)调用。
@estus 它只是我在节点中测试东西时的一个快速函数名称,不一定代表程序的 main
仅供参考,async/await 是 ES2017 的一部分,而不是 ES7 (ES2016)
对于交互式节点外壳 (REPL),请尝试 node --experimental-repl-await

T
T.J. Crowder

我似乎无法理解为什么这不起作用。

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

在顶层,您必须:

使用允许在模块中使用顶级等待的顶级等待(提案,MDN;ES2022,在现代环境中得到广泛支持)。或使用从不拒绝的顶级异步函数(除非您想要“未处理的拒绝”错误)。或使用 then 并捕获。

#1 模块中的顶级等待

您可以在模块的顶层使用 await。在您 await 的承诺完成之前,您的模块不会完成加载(这意味着任何等待您的模块加载的模块在承诺完成之前都不会完成加载)。如果 promise 被拒绝,您的模块将无法加载。通常,顶级 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 的模块被评估时,它会向模块加载器返回一个 Promise(就像 async 函数所做的那样),它会等到该 Promise 得到解决,然后再评估任何依赖它的模块的主体。

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

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

(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;您必须处理承诺拒绝/异步异常,因为没有其他方法可以处理;你没有调用者将它们传递给(与上面的#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 - 然后并抓住

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 回调都不 抛出任何错误,因为没有注册任何内容来处理它们。


将其视为一个承诺现在解释了为什么该函数立即返回。我尝试制作一个顶级匿名异步函数,我得到了现在有意义的结果
@Felipe:是的,async/await 是围绕承诺的语法糖(好糖 :-))。你不只是将它认为作为回报一个承诺;实际上确实如此。 (Details。)
我认为您不应该混合使用 async 和旧的 Promise.catch(),这会使代码更难阅读。如果你使用异步,你也应该使用常规的 try/catch。
@LukeMcGregor - 我在上面都展示了,首先是 all-async 选项。对于顶级函数,我可以看到任何一种方式(主要是因为 async 版本有两级缩进)。
@Felipe - 现在顶级 await 提案已进入第 3 阶段,我已经更新了答案。:-)
m
mikemaccana

Top-Level await 已移至第 3 阶段,因此您的问题我如何在顶层使用 async/await? 的答案是只使用 await

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

如果您想要一个 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)

兼容性

v8 自 2019 年 10 月起在 Chrome DevTools、Node.js 和 Safari 网络检查器中使用 REPL

Chrome DevTools、Node.js 和 Safari Web 检查器中的 REPL

标志后面的节点 v13.3+ --harmony-top-level-await

TypeScript 3.8+(问题)

Deno 自 2019 年 10 月起

Webpack@v5.0.0-alpha.15


--harmony-top-level-await 对我不起作用我在节点 14
@QuintenCabo 这可能是因为您没有使用 ES 模块。要确保 Node 处于模块模式,请将 "type": "module" 添加到 package.json
m
mikemaccana

2021回答:你现在可以在当前稳定版本的node中使用顶级await

上面的大多数答案都有些过时或非常冗长,所以这里有一个节点 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:

我正在使用 Next.js,这对我很有帮助:stackoverflow.com/a/68339259/470749
G
Gershom Maes

要在当前答案之上提供更多信息:

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,那么有两种可能的方法:

不要在函数内部直接使用 await 根本不要使用 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 的支持会很有趣!


[Node 在 v13.3 中添加了对顶级 await 的支持]stackoverflow.com/questions/59585793/…)。
w
wobsoriano

您现在可以在 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


该版本更改日志没有提及有关顶级等待的任何内容,而且似乎是 support for the flag started with v13.3
D
Duke Dougal

这个问题的实际解决方案是以不同的方式处理它。

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

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

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

这是您的应用程序的顶层应该是这样的:

import {application} from './server'

application();

你说得对,我的目标是初始化。诸如数据库连接、数据拉取等事情。在某些情况下,有必要在继续应用程序的其余部分之前获取用户的数据。本质上,您是在提议 application() 是异步的?
不,我只是说如果您的应用程序的根目录中只有一个 JavaScript 语句,那么您的问题就消失了 - 所示的顶级语句不是异步的。问题是不可能在顶层使用 async - 您无法在该级别实际等待 await - 因此,如果顶层只有一个语句,那么您已经回避了这个问题。您的初始化异步代码现在在一些导入的代码中关闭,因此异步可以正常工作,您可以在应用程序开始时初始化所有内容。
更正 - 应用程序是一个异步函数。
我不清楚对不起。关键是,通常,在顶层,异步函数不会等待...。 JavaScript 直接进入下一条语句,因此您无法确定您的 init 代码是否已完成。如果您的应用程序顶部只有一个语句,那没关系。
n
nomad

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

Deno - Deno 已经内置了它。


T
Tony O'Hagan

其他解决方案缺少 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);
  });

C
ChaseMoskal

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

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

A
Ali Raza

你需要在 package.json 中添加类型 "type": "module" 你很好。从'axios'导入axios; const res = await axios.get('https://api.github.com/users/wesbos');控制台.log(res.data);

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


D
Domus71

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


n
nassim miled

现在有了 ECMAScript22,我们可以在顶级模块中使用 await

这是一个 with 示例(await 顶级):

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

没有的另一个示例(await 顶级)

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

u
uingtea

对于浏览器,您需要添加 type="module"

没有type="module"

type="module"


P
Peracek

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

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

错误的。一旦所有的 Promise 都被接受或拒绝并且主线程中没有更多的代码在运行,该进程将自行终止。
@Dev:通常您希望将不同的值传递给 exit() 以指示是否发生错误。
@9000 是的,但这里没有这样做,并且由于退出代码 0 是默认值,因此无需包含它
@9000 事实上,错误处理程序可能应该使用 process.exit(1)