我一直在浏览 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
async/await
是 ES2017 的一部分,而不是 ES7 (ES2016)
node --experimental-repl-await
。
我似乎无法理解为什么这不起作用。
因为 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
回调都不 抛出任何错误,因为没有注册任何内容来处理它们。
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
"type": "module"
添加到 package.json
。
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:
要在当前答案之上提供更多信息:
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.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
这个问题的实际解决方案是以不同的方式处理它。
可能您的目标是某种初始化,这通常发生在应用程序的顶层。
解决方案是确保在您的应用程序的顶层只有一个 JavaScript 语句。如果您的应用程序顶部只有一个语句,那么您可以在任何其他位置自由使用 async/await(当然要遵守正常的语法规则)
换句话说,将你的整个顶层包装在一个函数中,这样它就不再是顶层,这解决了如何在应用程序的顶层运行 async/await 的问题——你不需要。
这是您的应用程序的顶层应该是这样的:
import {application} from './server'
application();
application()
是异步的?
节点 -
您可以在 REPL 中运行 node --experimental-repl-await
。我不太确定脚本。
Deno - Deno 已经内置了它。
其他解决方案缺少 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);
});
我喜欢这种巧妙的语法从入口点做异步工作
void async function main() {
await doSomeWork()
await doMoreWork()
}()
你需要在 package.json 中添加类型 "type": "module" 你很好。从'axios'导入axios; const res = await axios.get('https://api.github.com/users/wesbos');控制台.log(res.data);
请记住,如果您更改文档类型,那么您必须以 ES6 方式编写代码。
在 NodeJS 14.8+ 中,您可以使用顶级 await 模块(#3 解决方案)。您也可以将 .js 重命名为 .mjs(ES 模块)而不是 .js(.cjs CommonJS)。
现在有了 ECMAScript22,我们可以在顶级模块中使用 await
。
这是一个 with 示例(await
顶级):
const response = await fetch("...");
console.log(response):
没有的另一个示例(await
顶级)
async function callApi() {
const response = await fetch("...");
console.log(response)
}
callApi()
对于浏览器,您需要添加 type="module"
没有type="module"
与type="module"
由于 main()
异步运行,它返回一个承诺。您必须在 then()
方法中获得结果。而且因为 then()
也返回了 Promise,所以您必须调用 process.exit()
来结束程序。
main()
.then(
(text) => { console.log('outside: ' + text) },
(err) => { console.log(err) }
)
.then(() => { process.exit() } )
exit()
以指示是否发生错误。
process.exit(1)
async
/await
是围绕承诺的语法糖(好糖 :-))。你不只是将它认为作为回报一个承诺;实际上确实如此。 (Details。)Promise.catch()
,这会使代码更难阅读。如果你使用异步,你也应该使用常规的 try/catch。async
选项。对于顶级函数,我可以看到任何一种方式(主要是因为async
版本有两级缩进)。await
提案已进入第 3 阶段,我已经更新了答案。:-)