我已经阅读了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] > inside:嘿,大家好

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

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


当前回答

在当前答案的基础上提供一些进一步的信息:

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这个词!

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

不要在函数中直接使用await 完全不要使用await

选项1要求我们创建一个新的作用域(这个作用域可以是异步的,因为我们可以控制它):

// 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的支持会很有趣!

其他回答

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

因为主回承诺;所有异步函数都是这样。

在顶层,你必须:

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

模块中的#1顶级await

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

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

如果你的模块可以在promise被拒绝的情况下继续工作,你可以在try/catch中包装顶层的await:

// 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

注意其中的陷阱;你必须处理承诺拒绝/异步异常,因为没有其他事情要做;你没有调用者来传递它们(不像上面的#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和显式promise回调),否则我通常建议不要这样做。

当然,也可以不处理错误,只允许出现“未处理的拒绝”错误。

#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处理程序不会抛出错误,因为没有注册任何东西来处理它们。)

或者两个论证都是:

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

我喜欢这种聪明的语法来从入口点执行异步工作

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

因为main()是异步运行的,所以它返回一个承诺。你必须在then()方法中获取结果。因为then()也返回promise,所以必须调用process.exit()来结束程序。

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

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

如果您的唯一目标是为了测试目的控制混合了其他代码的异步代码的执行顺序,那么您可以将整个顶级代码包装在定义为异步函数的立即调用函数表达式(IIFE)中。在问题的示例中,在调用main()之前添加await。

当您的代码不在异步函数中或不在模块的顶层主体中时,可以使用此模式。换句话说,如果你只是在一个js文件中测试一堆代码,并使用Live Server、RunJs或任何其他类型的JavaScript工具来观察控制台窗口,将所有代码包装在定义为异步的IIFE中,当你想在执行下一行之前等待异步代码完成时,使用await关键字。

let topLevelIIFE = (async () => { 异步函数main() { var value = await承诺。解决('嘿'); Console.log ('inside: ' + value); 返回值; } Var文本=等待main(); Console.log ('outside: ' + text); }) ()

在Chrome DevTools或其他行为类似的浏览器REPL工具的REPL中运行IIFE主体中指定的代码时,您不需要使用此模式。