Home > database >  Await or not a sub function in javascript
Await or not a sub function in javascript

Time:02-01

What is the difference between:

async function subfunc(){
    await something()
}

async function func(){
    subfunc() // subfunc is not awaited but something inside it is
}

func()

and

async function subfunc(){
    await something()
}

async function func(){

    await subfunc() // subfunc is awaited and something inside it is also
}

func()

It sounds like it produces the same result, where am I wrong?

CodePudding user response:

It sounds like it produces the same result, where am I wrong?

The results are very different.

Without await, the promise returned by subfunc is not awaited, so func settles its promise without waiting for the promise from subfunc to settle. Several ramifications of that:

  • subfunc's work won't be done yet when func's promise is settled.
  • func can't use the fulfillment value of subfunc's promise (though your code using await doesn't use it either, so that may not matter to you).
  • if subfunc's promise is rejected, it doesn't cause rejection of func's promise; func's promise will already be fulfilled. In fact, nothing will handle the rejection of subfunc's promise, which will at a minimum trigger a console warning about an unhandled rejction or at a maxiumum could terminate the process the code is running in (Node.js does this by default, for instance).

(Re that third one: It happens that in the example you've given, nothing is handling rejection of func's promise either, but at least code calling func has the opportunity to handle rejection. Without the await of subfunc's promise, code calling func can't avoid a possible unhandled rejection.)

You can see some of that in these two examples (be sure to look int he real browser console):

Without await:

function something(flag) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (flag) {
                console.log("`subfunc` promise fulfilled");
                resolve();
            } else {
                console.log("`subfunc` promise rejected");
                reject(new Error("Some error here"));
            }
        }, 100);
    });
}

async function subfunc(flag) {
    await something(flag);
}

async function func(flag) {
    subfunc(flag); // No `await`
}

async function test(flag) {
    console.log("-");
    console.log(
        `Calling \`func\` to test ${flag ? "fulfillment" : "rejection"}:`
    );
    try {
        await func(flag);
        console.log("`func` promise fulfilled");
    } catch (error) {
        console.log("`func` promise rejected: ", error.message);
    }
}

console.log("WITHOUT `await`:");
test(true).then(() => {
    setTimeout(() => {
        test(false);
    }, 500);
});
.as-console-wrapper {
    max-height: 100% !important;
}

Notice the

Uncaught (in promise) Error: Some error here

or similar in the real browser console. That's the unhandled rejection.

With await:

function something(flag) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (flag) {
                console.log("`subfunc` promise fulfilled");
                resolve();
            } else {
                console.log("`subfunc` promise rejected");
                reject(new Error("Some error here"));
            }
        }, 100);
    });
}

async function subfunc(flag) {
    await something(flag);
}

async function func(flag) {
    await subfunc(flag);
}

async function test(flag) {
    console.log("-");
    console.log(
        `Calling \`func\` to test ${flag ? "fulfillment" : "rejection"}:`
    );
    try {
        await func(flag);
        console.log("`func` promise fulfilled");
    } catch (error) {
        console.log("`func` promise rejected: ", error.message);
    }
}

console.log("WITH `await`:");
test(true).then(() => {
    setTimeout(() => {
        test(false);
    }, 500);
});
.as-console-wrapper {
    max-height: 100% !important;
}


In a comment you asked:

this is what I don't get, we you execute subfunc without await, something() is awaited anyway no?

That's a very good observation. Yes, something() is awaited in subfunc (so subfunc doesn't settle its promise until the promise it gets from something() settles), but that doesn't make func wait for subfunc to settle its promise.

It may help to walk through the execution of your first code block (the one without await in func), starting where the main script calls func. (Please note that I've omitted some details for clarity.)

  1. func calls subfunc:
    1. subfunc calls something and gets back a value, let's call it pSomething. (Let's assume the normal case: pSomething is a promise, and it's still pending.¹)
    2. subfunc uses await on pSomething:
      1. subfunc creates a promise, let's call it pSubfunc.
      2. await attaches fulfilment and rejection handlers to pSomething. (More on what these handlers do later.)
      3. subfunc returns pSubfunc.
    3. Now that subfunc has returned, func returns, since it's not doing anything with subfunc's promise:
      1. func creates a promise (let's call it pFunc).
      2. func fulfills pFunc with undefined.
      3. func returns pFunc.
  2. It's possible nothing further happens because pSomething is never settled, but let's assume at some point it gets fulfilled:
    1. pSomething's settlement calls the fulfilment handler attached in subfunc in Step 1.2.2. That fulfilment handler contains the (implicit) code following the await in subfunc, which is return;. That code fulfills pSubfunc with undefined.

So as you can see, you're right that pSomething is awaited by subfunc, and so pSubfunc doesn't settle until pSomething settles. But func doesn't await pSubfunc, so func fulfills pFunc as soon as subfunc returns its still-pending promise.

And that's the crucial difference in the code: func doesn't wait for subfunc to finish its work, even though subfunc waits for something to finish its work.

¹ If pSomething weren't a promise, await would create and fulfill a new promise using the non-promise value it received as the fulfilment value and use that instead, which would change a couple of minor details in the explanation above, but not in ways that your code could easily observe.

  • Related