如何在回调函数中使用await?

4

我的代码如下:

async function run() {
  await sleep(1);
  fs.readFile('list.txt', 'utf8', function (err, text) {
    console.log(text);
    await sleep(5);
   });
}

run();

"await sleep(1)"是可以的,但"await sleep(5)"会导致以下错误:

SyntaxError: async is only valid in async function

这是因为fs.readFile造成的吗?在这种情况下,如何使用await?以上内容是为了测试而简化的示例。在我的实际用法中,我需要将await sleep放在一个嵌套了几个异步函数的回调函数中。"

1
关于你传递给readFile的第三个参数,它是一个函数。由于你在该函数内使用了await,因此它也必须是异步的。 - kennyzx
1
尝试在readFile参数内的函数前添加async,像这样:fs.readFile('list.txt', 'utf8', async function (err, text) ... - Yashar Aliabbasi
fs.readFile() 的回调函数不是异步函数。只有异步函数才能使用 await - Andrew Li
你确定你发布了正确的错误信息吗? - Code-Apprentice
4个回答

4

我不确定sleep函数具体是做什么的,但你问题中出现的原因是因为fs.readfile的回调函数不是异步函数,而await sleep(5)就在这里。

async function run() {
    await sleep(1);
    fs.readFile('list.txt', 'utf8', function (err, text) {
    console.log(text);           /* ^this isn't an async function */
    await sleep(5);
    });
}

您可以通过将异步函数作为回调函数传递来消除该错误,但是不建议混合使用Promise和回调流,这会导致问题,特别是在处理错误时。它会阻止您链接Promise并从run()外部捕获内部等待的错误。

更好的方法是将fs.readFile()封装在一个Promise中并等待它。

async function run() {
  await sleep(1);
  const text = await new Promise((resolve, reject) => {
    fs.readFile('list.txt', 'utf8', function (err, text) {
      if (err) reject(err) else resolve(text);
    });
  });
  console.log(text);
  await sleep(5);
}

这将使你能够以更加健壮的方式检测任何错误,具体操作如下:

使用以下代码:

run()
.then(() => /*...*/)
.catch(err => /* handle error */

并避免未处理的拒绝。


3
通常我不会对同级答案进行负面评价,但接受的答案是错误的。它可能演示了如何在回调中使用 await,但在实际应用中这样做是不可取的,也不应该向任何人推荐。这就是为什么会出现UnhandledPromiseRejectionWarning和“为什么我在 await run() 时有竞争条件”的问题。首先,await sleep(5) 不能影响任何东西。 - Estus Flask
@estus,你是正确的。看着你更加合理的解决方案,我会删除这个回答,但由于它已被选中,我无法这样做。如果你想编辑它以便未来用户使用,我不介意(我也可以这样做,但那只是明显地抄袭你)。 - Mark
1
当然,随意更新答案,我会撤回我的投票。对于用户来说,首先看到一个可用的答案总是更好的,这也是大多数用户看到的被接受的答案的方式。 - Estus Flask

4
上面是一个简化的样例用于测试。在我的实际使用中,我需要将await sleep放在一个嵌套了几个更多异步函数的回调中。
当可以使用promise时(如果回调被调用多次可能不可行),不应该使用async..await或Promise等来增强基于回调的API。这会导致控制流和错误处理能力较差。
一旦run中有async回调函数,就无法使用run().then(...)链接嵌套的promise。来自嵌套的promise的错误可能仍未被处理,从而导致UnhandledPromiseRejectionWarning。
正确的方式是从回调移动到promise并使用promise控制流。通常可以使用promise构造函数来实现:
async function run() {
  await sleep(1);
  const text = await new Promise((resolve, reject) => {
    fs.readFile('list.txt', 'utf8', function (err, text) {
      if (err) reject(err) else resolve(text);
    });
  });
  console.log(text);
  await sleep(5);
}

许多方法可以从基于回调的Node API获取Promise,例如自Node 10开始,fs提供了Promise API:

async function run() {
  await sleep(1);
  const text = await fs.promises.readFile('list.txt', 'utf8');
  console.log(text);
  await sleep(5);
}

1
如果您的函数包含“await”,则必须在函数声明前加上“async”。
注意:将函数设置为“async”函数后,始终会返回一个Promise。因此,如果您返回一个字符串,该字符串将自动包装在Promise对象中。
因此,您可以手动返回Promise(以增加清晰度)。一个简单的已解决的Promise就足够了:
async function foo(){
    await bar();
    return Promise.resolve('optional'); // optionally resolve with a value
}

关键字 "async" 总是在关键字 "function" 之前。
var foo = async function(){
    await bar();
    return Promise.resolve('optional'); // optionally resolve with a value
}

"...而对于箭头函数,则很好:"
var foo = sally( async () => {
    await bar();
    return Promise.resolve('optional'); // optionally resolve with a value
})

要检索返回值:

foo().then(val => console.log(val) ); // prints "optional" to console

通过将您的函数设置为“async”函数,您需要返回一个Promise。async函数总是将返回值包装在一个Promise中。因此,您的示例等同于return 'optional'; - Felix Kling
@felix 谢谢你的迅速回复,Promise.resolve() 返回一个 Promise 对象,因为我们直接在原生的 window.Promise 上调用了 "resolve" 方法。这只是一种快速、便宜的创建简单 Promise 的方法。 - bob
我认为Felix的观点是,在async函数中不需要显式返回一个promise。不仅没有必要,而且这会破坏async-await的目的,因为async函数的重点是它隐式地返回一个包装你在函数中返回或抛出的任何内容的promise。 - Lennholm
同意,但是新手需要注意,async函数会改变任何返回值,而这些返回值不是promise类型的。Felix暗示了返回值为string类型而不是promise类型。 - bob

0

在函数开始之前,您必须使用aysnc关键字。 await关键字无法与普通函数一起使用。

async function run() {
  await sleep(1);
  fs.readFile('list.txt', 'utf8', async function (err, text) {
    console.log(text);
    await sleep(5);
   });
}

run();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接