如何在顶级使用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):

>outside: [object Promise]

>内部:嘿,你好

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

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

280035 次浏览

我不明白为什么这行不通。

因为main返回一个承诺;所有async函数都是这样。

在顶层,你必须:

  1. 使用顶级await (建议中数;ES2022,现代环境中的广泛的支持),允许在模块中顶层使用await

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

  3. 使用thencatch

模块中#1顶级await

你可以在模块的顶层使用await。你的模块将不会完成加载,直到承诺你await解决(意味着任何模块等待你的模块加载将不会完成加载,直到承诺解决)。如果承诺被拒绝,您的模块将无法加载。通常,顶层await被用于这样的情况:你的模块在承诺被解决之前无法完成它的工作,除非承诺被实现,否则根本无法完成它,所以这很好:

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

如果你的模块即使承诺被拒绝也能继续工作,你可以将顶层的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的模块被求值时,它会向模块加载器返回一个承诺(就像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;you 必须处理承诺拒绝/异步异常,因为没有其他事情要做;你没有呼叫者可以把它们传递给(不像上面的第一条,你的“呼叫者”;是模块加载器)。如果你愿意,你可以通过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()是异步运行的,所以它返回一个承诺。你必须在then()方法中获取结果。因为then()也返回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 !

但是如果你需要使用这个文件中的承诺,那么有两种可能的方法:

  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要求我们使用面向对象的承诺API(使用承诺的不太漂亮但功能相同的范例)

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


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

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

顶级await已经移动到第三阶段阶段4(参见南无的评论),所以你的问题如何在顶级使用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)

兼容性

你现在可以在节点v13.3.0中使用顶级await

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退出状态,失败时报告非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);
});

如果你正在使用像指挥官这样的命令行解析器,你可能不需要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(。cj 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,我们可以在顶级模块中使用await

这是一个例子 (await顶级):

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

另一个例子没有 (await顶级)

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

对于Browser,你需要添加type="module"

没有type="module"

<script>
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await resp.json();
console.log(users)
</script>

type="module"

<!--script type="module" src="await.js" -->
<script type="module">
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await resp.json();
console.log(users)
</script>