1

I'm a bit confused about async functions. It says that they return a promise. A promise usually does not immediately run before the rest of the code, because javascript works with the "run to completion" approach.

But if async functions return a promise, how can you explain this?


const _prefixJS = async file => {
  console.log('running...')
  fs.copyFileSync(file, `${file}.bak`);
  const content = fs.readFileSync(file, 'utf8');
  // Some modifications...
  fs.writeFileSync(file, newContent);
  console.log('stopping!')
};

console.log('start')
_prefixJS(f);
console.log('end')

// Output: start > running... > stopping! > end

In my opinion, the output should be: start > end > starting... > stopping!

Because promises run concurrently and they are placed at the end of the event loop.

I know that those methods are still synchronous, but that's not the topic. I'm currently going from synchronized nodejs-methods to async ones. I am just asking that if async methods return a promise, how is it that it runs before the end console.log statement?

codepleb
  • 10,086
  • 14
  • 69
  • 111
  • 1
    you have zero `async` functions inside your `_prefixJS` So the browser is likely optimising out the wasteful `async` keyword. – Keith Dec 09 '19 at 16:43
  • 6
    Your code does not actually perform any async work. – Igor Dec 09 '19 at 16:43
  • Look at the `copyFileSync` function that you're using. That **Sync** part means synchronous. – zero298 Dec 09 '19 at 16:44
  • 1
    This is *cooperative* multitasking - you need to `await` somewhere to return control to the main thread. – jonrsharpe Dec 09 '19 at 16:44
  • You're using *Sync versions, which are explicitly synchronous. – Tim Marinin Dec 09 '19 at 16:44
  • @devlincarnate — Functions declared with the `async` keyword always return a promise. – Quentin Dec 09 '19 at 16:46
  • @Keith Where do you see an explanation for this: `start > running... > stopping! > end`. Because I cannot find it in the comments. This question has nothing to do with the sync methods I'm currently using. But I guess it was a bit of a misleading example. – codepleb Dec 09 '19 at 16:51
  • "A promise usually does not immediately run before the rest of the code" — Yes, it does. Usually, you just don't notice because the promise does something asynchronous and returns control to the parent function before there's any *measurable* change. – Quentin Dec 09 '19 at 16:58
  • @Keith Ah ok sorry. Then I misinterpreted your comment. – codepleb Dec 09 '19 at 17:00
  • @Quentin Good point, but I assume he meant when a promise gets resolved. I think the confusion might be that in the early days, I can't remember when, but promises were always assumed to be next tick, but I assume because of performance reasons this restriction was taking out. I think people even used this side effect to clear down the stack, but with tail recursion this is likely not an issue anymore. – Keith Dec 09 '19 at 17:02

1 Answers1

6

But if async functions return a promise, how can you explain this?

An async function runs synchronously up to the first await or return (including implicit return). (When I say "up to," the synchronous part includes the operand to the right of await or return.) At that point (await or return), it returns a promise. That's so it can start whatever asynchronous process it's supposed to start. This is covered in the specification here.

Your _prefixJS doesn't contain any await or return, so it synchronously runs through to the end, returning a promise that will be fulfilled with the value undefined.

To make your async function actually work asynchronously, you'd want to use the fs.promises version of those functions and await their results. Something like this (untested):

const fsp = require("fs").promises;
// ...
const _prefixJS = async file => {
  console.log('running...');
  await fsp.copyFile(file, `${file}.bak`);
  const content = await fsp.readFile(file, 'utf8');
  // Some modifications...
  await fsp.writeFile(file, newContent);
  console.log('stopping!');
};

With that function, the console.log('running'); call and the fsp.copyFile(...) call are done synchronously when _prefixJS is called, then the function returns its promise and waits for the result of fsp.copyFile(...) before continuing its logic asynchronously.

Live example using placeholders for the fsp functions:

const doWork = () => new Promise(resolve =>
    setTimeout(resolve, Math.random() * 2000));

const fsp = {
    async copyFile() {
        console.log("copyFile started");
        await doWork();
        console.log("copyFile returning");
    },
    async readFile() {
        console.log("readFile started");
        await doWork();
        console.log("readFile returning");
    },
    async writeFile() {
        console.log("writeFile started");
        await doWork();
        console.log("writeFile returning");
    }
};

const _prefixJS = async file => {
    console.log('running...');
    await fsp.copyFile(file, `${file}.bak`);
    const content = await fsp.readFile(file, 'utf8');
    // Some modifications...
    await fsp.writeFile(file, "newContent");
    console.log('stopping!');
};

console.log("calling _prefixJS");
_prefixJS()
.then(() => {
    console.log("then handler on _prefixJS()");
})
.catch(error => {
    console.log(`catch handler on _prefixJS(): ${error.message}`);
});
console.log("calling _prefixJS - done");
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875