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.
2
Answers
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 anawait
. 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 athen
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.When you return a promise in an
async
function, there are two promises involved: the promise in thereturn
statement (let’s call it A), and the promise returned by theasync
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 athen
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 anasync
function that executes areturn
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 thereturn
statement.An async function executes synchronously until it either throws outside a catch statement, returns a value, or executes an
await
statement.if it executes an
await
operator it returns a pending promise which will be rejected later if theawait
operator or function code following it throws without being caught, or which will be fulfilled with the function’s ultimate return value if neither theawait
operator nor any code following it throws an uncaught error.If the async function call throws an uncaught error before
await
is encountered, the function synchronously returns a rejected promise with the error as its rejection reason.If the async function returns synchonously with error (and no
await
operator was encountered), it returns a fulfilled promise with the return value as promise data.There are no other cases to consider.