1

Hi guys~ I found an interesting thing with the return value of async function.

There are two codes:

async function test () {
  return Promise.resolve(undefined)
}

async function test () {
  return undefined
}

So what's differences between them?

If you say, 'They are the same thing' just like I did, then I'm confused with this code:

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log('async2 start');
   // return Promise.resolve();
}
async1();
new Promise(function (resolve) {
  console.log("Promise 1 start");
  resolve();
}).then(function () {
  console.log("Then 1");
}).then(function () {
  console.log("Then 2");
}).then(function () {
  console.log("Then 3");
}).then(function () {
  console.log("Then 4");
});

In Chrome 73 you will get:

async1 start
async2 start
Promise 1 start
async1 end
Then 1
Then 2
Then 3
Then 4

Now if we return Promise.resolve() in the async2 function:

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log('async2 start');
  return Promise.resolve();
}
async1();
new Promise(function (resolve) {
  console.log("Promise 1 start");
  resolve();
}).then(function () {
  console.log("Then 1");
}).then(function () {
  console.log("Then 2");
}).then(function () {
  console.log("Then 3");
}).then(function () {
  console.log("Then 4");
});

In Chrome 73 you will get:

async1 start
async2 start
Promise 1 start
Then 1
Then 2
async1 end
Then 3
Then 4

amazing? Notice that, the async1 end is different with the first situation just because we return Promise.resolve() in async2 function.

By the way, the Code in Chrome 70 is different with Chrome 73.

The first in Chrome 70 you will get

async1 start
async2 start
Promise 1 start
Then 1
Then 2
async1 end
Then 3
Then 4

And the second in Chrome 70 you will get

async1 start
async2 start
Promise 1 start
Then 1
Then 2
Then 3
async1 end
Then 4

The difference between Chrome 70 & 73 may be the reason in this blog: https://v8.dev/blog/fast-async

So the Chrome 73 seems be the right behavior in the future. But I'm still confused, what's the differences between return undefined & return Promise.resolve() in async function?

SZ MARK
  • 11
  • 1
  • If you want to have even _more_ fun, remove `async` from the `async2()` call in the `async1()` function that _does not return a promise_ (your first example). – Randy Casburn Dec 12 '18 at 03:27
  • Unless I’m missing something, isn’t this just due to the nature if it being asynchronous? – Krease Dec 12 '18 at 03:29
  • _"So what's differences between them?"_ [explicit promise creation anti-pattern](https://stackoverflow.com/q/23803743/283366). TL;DR - `return ...` resolves the async function return value (a promise). `return Promise.resolve(...)` does the same only it resolves with another promise that itself resolves immediately – Phil Dec 12 '18 at 03:44
  • @Phil If so, `return Promise.resolve()` will be more faster than `return undefined`.While the result is the opposite – SZ MARK Dec 12 '18 at 03:58

1 Answers1

0

They both do return exactly the same thing, just at different times of the event loop.

Referencing the spec: (Run the code snippet to see the spec section.)

As you can see, in step 3.d.1 the Promise is resolved immediately (as step 3.b. says Assert: If we return here. (As your first example does)

On the other hand, when a Promise is returned the sequence continues on until after Step 7 runs the AsyncFunctionAwait (25.5.5.3) and finally in step 8 is returned. (as your second example does)

Here is the reference from the spec: https://www.ecma-international.org/ecma-262/8.0/#sec-async-functions-abstract-operations-async-function-start.

<emu-clause id="sec-async-functions-abstract-operations-async-function-start" aoid="AsyncFunctionStart">
  <h1><span class="secnum">25.5.5.2</span>AsyncFunctionStart ( <var>promiseCapability</var>, <var>asyncFunctionBody</var> )</h1>
  <emu-alg>
    <ol>
      <li>Let <var>runningContext</var> be the
        <emu-xref href="#running-execution-context" id="_ref_6309"><a href="#running-execution-context">running execution context</a></emu-xref>.</li>
      <li>Let <var>asyncContext</var> be a copy of <var>runningContext</var>.</li>
      <li>Set the code evaluation state of <var>asyncContext</var> such that when evaluation is resumed for that
        <emu-xref href="#sec-execution-contexts" id="_ref_6310"><a href="#sec-execution-contexts">execution context</a></emu-xref> the following steps will be performed:
        <ol type="a">
          <li>Let <var>result</var> be the result of evaluating <var>asyncFunctionBody</var>.</li>
          <li>Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.</li>
          <li>Remove <var>asyncContext</var> from the
            <emu-xref href="#execution-context-stack" id="_ref_6311"><a href="#execution-context-stack">execution context stack</a></emu-xref> and restore the
            <emu-xref href="#sec-execution-contexts" id="_ref_6312"><a href="#sec-execution-contexts">execution context</a></emu-xref> that is at the top of the
            <emu-xref href="#execution-context-stack" id="_ref_6313"><a href="#execution-context-stack">execution context stack</a></emu-xref> as the
            <emu-xref href="#running-execution-context" id="_ref_6314"><a href="#running-execution-context">running execution context</a></emu-xref>.</li>
          <li>If <var>result</var>.[[Type]] is
            <emu-const>normal</emu-const>, then
            <ol>
              <li>Perform !&nbsp;
                <emu-xref aoid="Call" id="_ref_6315"><a href="#sec-call">Call</a></emu-xref>(<var>promiseCapability</var>.[[Resolve]],
                <emu-val>undefined</emu-val>, «
                <emu-val>undefined</emu-val>»).</li>
            </ol>
          </li>
          <li>Else if <var>result</var>.[[Type]] is
            <emu-const>return</emu-const>, then
            <ol>
              <li>Perform !&nbsp;
                <emu-xref aoid="Call" id="_ref_6316"><a href="#sec-call">Call</a></emu-xref>(<var>promiseCapability</var>.[[Resolve]],
                <emu-val>undefined</emu-val>, «<var>result</var>.[[Value]]»).</li>
            </ol>
          </li>
          <li>Else,
            <ol>
              <li>Assert: <var>result</var>.[[Type]] is
                <emu-const>throw</emu-const>.</li>
              <li>Perform !&nbsp;
                <emu-xref aoid="Call" id="_ref_6317"><a href="#sec-call">Call</a></emu-xref>(<var>promiseCapability</var>.[[Reject]],
                <emu-val>undefined</emu-val>, «<var>result</var>.[[Value]]»).</li>
            </ol>
          </li>
          <li>Return.</li>
        </ol>
      </li>
      <li>Push <var>asyncContext</var> onto the
        <emu-xref href="#execution-context-stack" id="_ref_6318"><a href="#execution-context-stack">execution context stack</a></emu-xref>; <var>asyncContext</var> is now the
        <emu-xref href="#running-execution-context" id="_ref_6319"><a href="#running-execution-context">running execution context</a></emu-xref>.</li>
      <li>Resume the suspended evaluation of <var>asyncContext</var>. Let <var>result</var> be the value returned by the resumed computation.</li>
      <li>Assert: When we return here, <var>asyncContext</var> has already been removed from the
        <emu-xref href="#execution-context-stack" id="_ref_6320"><a href="#execution-context-stack">execution context stack</a></emu-xref> and <var>runningContext</var> is the currently
        <emu-xref href="#running-execution-context" id="_ref_6321"><a href="#running-execution-context">running execution context</a></emu-xref>.</li>
      <li>Assert: <var>result</var> is a normal completion with a value of
        <emu-val>undefined</emu-val>. The possible sources of completion values are
        <emu-xref aoid="AsyncFunctionAwait" id="_ref_6322"><a href="#sec-async-functions-abstract-operations-async-function-await">AsyncFunctionAwait</a></emu-xref> or, if the async function doesn't await anything, the step 3.g above.</li>
      <li>Return.
      </li>
    </ol>
  </emu-alg>
</emu-clause>
Randy Casburn
  • 13,840
  • 1
  • 16
  • 31
  • But why is it slower in timing than 2 instead of 1 microtask? – SZ MARK Dec 12 '18 at 09:05
  • The extra internal processing (internal function calls, creation of internal objects, etc.) delay the second example's Promise microtask from getting onto the queue sooner - hence it completes later. – Randy Casburn Dec 12 '18 at 14:49
  • I can understand why it is slow, but it is slower with two microtasks, that's why I don't understand. I think it should only be slower than one microtask? – SZ MARK Dec 13 '18 at 01:57