javascriptnode.jsasynchronousevent-loop

How is the promise settled by the time it is logged?


I have the following code:

async function get_exam_score() {
    const exam_score = Math.random() * 10;
    if (exam_score < 5) {
        throw("You failed");
    }
}

const promise = get_exam_score();
console.log("TRY:", promise);

and if I run that above code, console.log() shows that the async function promise has already been settled with either a fulfilled or rejected state. Code & execution

How is that possible? Should not the promise still be in a pending state since its microtask with the result should not be able to execute until the callstack is empty?

I would like to know in-depth how is JavaScript managing the callstack, microtask queue, event loop and anything relevant to this topic. So that it leads to the promise being in a settled state by the time console.log() is executed.

Interestingly enough, if I change the function get_exam_score to explicitly return a Promise. Then, I do get that the promise is indeed in a pending state. Code & execution result

async function get_exam_score() {
    return new Promise((resolve, reject) => {
        const exam_score = Math.random() * 10;
        if (exam_score < 5) {
            reject("You failed");
        }
        resolve("You passed");
    });
}

const promise = get_exam_score();
console.log("TRY:", promise);

Why are these 2 code samples generating different outputs?


EDIT: Removed try / catch block as it was misleading. Additionally, added a code example which actually returns a promise with a pending status.


Solution

  • Should not the promise still be in a pending state since its microtask with the result should not be able to execute until the callstack is empty?

    That promise is never in a pending state. It is settled from the start. But JS code can only know about that state by registering a then callback (its first or second argument) -- or issuing an await. The microtask that is queued to get that callback executed is only informing about the promise's state and value. That happens asynchronously, even though the promise's state can be set synchronously. The agent (browser) knows about the promise's state without attaching a then callback, which is why the console can show the promise's state synchronously, but with JS code it is not possible to inspect a promise's state synchronously.

    If I change the function get_exam_score to explicitly return a Promise. Then, I do get that the promise is indeed in a pending state.

    When you return a promise in an async function, there are two promises involved: the promise in the return statement (let's call it A), and the promise returned by the async function (let's call it B). Those are distinct promises. Promise B is "locked-in" to promise A. Implicitly this is done "behind the scenes" by executing a then method call on promise A and have a callback resolve promise B in the same way as promise A.

    As this then-callback executes asynchronously, we can conclude that an async function that executes a return statement with a promise as operand always returns a pending promise -- no matter what the initial state is of the promise that is given to the return statement.