4

My code is:

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

run();

await sleep(1) is fine, but await sleep(5) results in:

SyntaxError: async is only valid in async function

Is fs.readFile the problem? How do I use await in such a situation?

The above is a reduced example to test. In my actual usage, I need to put the await sleep in a callback deeply nested inside a few more asynchronous functions.

Old Geezer
  • 14,854
  • 31
  • 111
  • 198

4 Answers4

4

I'm not sure what sleep is doing, but the problem in your question is happening because the callback to fs.readfile is not an async function and the that is where await sleep(5) is.

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);
    });
}

You could make that error go away by passing an async function as a callback, however mixing promise and callback flows is not recommended and leads to problems, especially when handling errors. It prevents you from chaining the promise and catching errors from the inner await outside of run().

A better approach is to wrap fs.readFile() in a promise and await that.

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);
}

This will allow you to catch any errors in much more robust way with:

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

and avoid unhandled rejections.

Mark
  • 90,562
  • 7
  • 108
  • 148
  • 3
    Usually I don't downvote sibling answers, but the accepted answer is just wrong. It may illustrate how it's possible to use `await` in a callback but this should never be done in real life and shouldn't be recommended to no one. That's where UnhandledPromiseRejectionWarning and 'why do I have race condition with await run()' kind of questions come from. `await sleep(5)` can't affect anything, for starters. – Estus Flask Aug 12 '18 at 12:43
  • @estus, you are correct. Looking at your much more sensible solution, I would delete this if I could, but since it’s selected, I can’t. If you would like to edit it so it’s correct for future users, I wouldn’t mind (I could do that too, but I’d just be blatantly copying you.) – Mark Aug 12 '18 at 15:28
  • 1
    Sure, feel free to update the answer, I'll retract my vote. It's always better for users to see a usable answer first, and that's how accepted answer is shown to most users. – Estus Flask Aug 12 '18 at 15:35
4

The above is a reduced example to test. In my actual usage, I need to put the await sleep in a callback deeply nested inside a few more asynchronous functions.

Callback based APIs shouldn't be augmented with async..await or promises in general when there is a chance to stick to promises (may not be possible if a callback is called more than once). This results in poor control flow and error handling.

Once there is async callback function inside run, it's impossible to chain nested promise with run().then(...). Errors from nested promises may remain unhandled as well and result in UnhandledPromiseRejectionWarning.

The proper way is to move from callbacks to promises and use promise control flow. Generally this can be achieved with promise constructor:

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);
}

There are numerous ways to get promises from Node callback based APIs, e.g. fs provides promise API since Node 10:

async function run() {
  await sleep(1);
  const text = await fs.promises.readFile('list.txt', 'utf8');
  console.log(text);
  await sleep(5);
}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
1

If your function includes an "await", you must prefix your function declaration with "async".

NOTE: By making your function an "async" function a promise is always returned. So if you return a string, that string will get automatically wrapped in a Promise object.

You can therefore return a Promise manually (for clarity). A simple resolved promise is sufficient:

async function foo(){
    await bar();
    return Promise.resolve('optional'); // optionally resolve with a value
}

The keyword "async" always goes before the keyword "function":

var foo = async function(){
    await bar();
    return Promise.resolve('optional'); // optionally resolve with a value
}

... and well for arrow functions:

var foo = sally( async () => {
    await bar();
    return Promise.resolve('optional'); // optionally resolve with a value
})

To retrieve the return value:

foo().then(val => console.log(val) ); // prints "optional" to console
bob
  • 7,539
  • 2
  • 46
  • 42
  • *"And by making your function an "async" function, you'll need to return a promise. "* `async` functions always wrap the return value in a promise. So your examples are equivalent to `return 'optional';`. – Felix Kling Aug 12 '18 at 04:02
  • @felix Thanks for the prompt, the Promise.resolve() returns a Promise object because we're calling the "resolve" method directly off the native window.Promise. It's just a fast, cheap way to create a simple Promise. – bob Aug 12 '18 at 04:27
  • I think Felix's point was that you do NOT need to explicitly return a promise in an `async` function. Not only is it unnecessary but also defeats the purpose of async-await, since the point of an `async` function is that it implicitly returns a promise that wraps whatever you return (or throw) in the function. – Lennholm Aug 12 '18 at 13:07
  • Agreed, but noobs need to be aware that an ```async``` function alters any returned value that is not a promise. Felix implied that the return value was a ```typeof string``` and not a ```typeof promise```. – bob Aug 14 '18 at 01:15
0

You have to use aysnc key before function start. await keyword will not work with normal function.

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

run();
Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
Himanshu Gupta
  • 382
  • 4
  • 15