Author: Mountain ant

Promises are all too familiar — you can do the simple stuff, but a tricky interviewer might ask a different question, like ask a few questions and let you see how they work out.

Let’s take a closer look at Promise’s sign-up microtask and implementation process. Use promises correctly and know why. No longer afraid of the interviewer’s Promise questions.

  • Five pieces of code delve into Promise’s registration microtask and code execution
  • Analyze the implementation differences between Promise/A+ and WebKit (chrome and Safari kernel) kernel JS engine Promise
  • Let’s consolidate. Let’s do a problem

We usually learn Promises based on Promises/A+ implementations. But I have to tell you that this article will also analyze the differences between this JS implementation and the WebKit kernel’s JS engine’s Promise implementation. Specific to how the code works.

The overall idea of this article adopts the code example + analysis to explain the way to interpret, so that everyone can understand, everyone can understand the purpose.

If you read all of this, the process of signing up for and implementing promises will become so ingrained in you that you are a Promise god. ~ ~ ~ ~ ~ ~ ~

preface

This article has a code-readable way to learn the entire process. Here are five pieces of code, and if you can understand all of them correctly and say the output process, you’ll be a great guy, like you, and I’ll give you a thumbs up for knowing how to execute the Promise.

Of course, you may not really understand the core, can correctly understand and explain the process, might as well look at the explanation of the problem. Of course, if you are a rookie like me, then we will take a look together ~

Before you look at the answer, work on the output yourself.

First piece of code

new Promise((resolve, reject) = > {
  console.log("External promise");
  resolve();
})
  .then((a)= > {
    console.log("External first then");
    return new Promise((resolve, reject) = > {
      console.log("Internal promise");
      resolve();
    })
    .then((a)= > {
    console.log("First inside then");
    })
    .then((a)= > {
    console.log("Internal second then");
    });
  })
  .then((a)= > {
    console.log("External second then");
  });

Copy the code

The output is simple: the first external new Promise is executed, the resolve is completed, and the first external then is executed. The first external THEN method returns a Promise. This return represents that the execution of the second external THEN must wait for the result after the return. Of course, after the internal two THEN’s are executed, the external second then is executed, which, like you, is exactly right.

output:

External PROMISE External first THEN Internal promise internal first THEN internal second THEN External second THENCopy the code

This is the second piece of code

new Promise((resolve, reject) = > {
  console.log("External promise");
  resolve();
})
  .then((a)= > {
    console.log("External first then");
    new Promise((resolve, reject) = > {
      console.log("Internal promise");
      resolve();
    })
      .then((a)= > {
        console.log("First inside then");
      })
      .then((a)= > {
        console.log("Internal second then");
      });
  })
  .then((a)= > {
    console.log("External second then");
  });
Copy the code

This code differs from the first code by a return, but the result is different.

So what does this mean?

We know that the event mechanism is “first registered, first executed”, that is, the “queue” pattern in the data structure, first in first out. So let’s see which of them signed up first.

The registration of the second external THEN waits for the synchronization code of the first external THEN to complete. When an internal new Promise is executed and the resolve state is reached, the resolve state is reversed, and the first internal.then microtask is registered. We know that the action to be performed is a microtask, so it is natural to complete the synchronization task first, such as the following:

new Promise((resolve, reject) = > {
    resolve();
    console.log(111);
})
.then((a)= > {
    consle.log(222);
})
Copy the code

This code obviously outputs 1111, followed by 222. Because the output of 222 is microtask execution, 111 is synchronous execution.

The new Promise’s first “then” registration is executed. The new Promise’s first “then” registration is synchronized to the “. Then “registration.

However, the internal second THEN is determined by the execution of the first THEN, and the first THEN callback is not executed, only the registration of the synchronized.then method is executed, so it enters the wait state.

At this point, the first external THEN operation is completed, and the second external THEN is registered. At this point, the external synchronization task is completed. After the synchronization is done, then we start to execute the microtask, and we see that the first internal then is a registration that takes precedence over the second external THEN, so we’ll execute the first internal THEN, and then register the second internal THEN, Then the external second THEN is executed, and then the internal second THEN is executed.

output:

External PROMISE External first THEN Internal promise internal first THEN external second THEN internal second THENCopy the code

As we can see, it is obvious that a THEN is executed, and the next THEN after that then is registered. According to the principle of task queue, we can see that the internal and external THEN are executed alternately, and then registered alternately. That’s why there is an alternate output of inside and outside content. In addition, by registering then, I mean registering the microtask queue, not the execution of the.then method. In fact, the execution of the.then method can be interpreted as merely initialization. If you look at the source code, the execution of the.then is indeed synchronous. Internally, a new Promise is opened, but because the previous state is unstreamed, the then is not registered in the microtask queue at this time, and waits for the last execution to complete. So it’s ok for us to interpret.then not registering a microtask as not being executed yet.

Now look at the third piece of code

new Promise((resolve, reject) = > {
  console.log("External promise");
  resolve();
})
  .then((a)= > {
    console.log("External first then");
    let p = new Promise((resolve, reject) = > {
      console.log("Internal promise");
      resolve();
    })
    p.then((a)= > {
        console.log("First inside then");
      })
    p.then((a)= > {
        console.log("Internal second then");
      });
  })
  .then((a)= > {
    console.log("External second then");
  });
Copy the code

The difference in this code is that the code inside the Promise is written differently, not as a chained call.

What do we mean here?

The new Promise’s resolve state is reversed. The new Promise’s resolve state is reversed. The new Promise’s resolve state is reversed.

The main differences between the two approaches are:

  • The registration of chained calls is pre-dependent, such as the registration of the external second THEN above, which requires the execution of the external first THEN.
  • The way the variables are defined, the registration is synchronized so for example, p.hen and var p = new Promise are executed synchronously.

So the execution of the code here is clear. After the internal execution is complete (because it takes precedence over the registration of the external second THEN), the external second THEN is executed:

output:

External PROMISE External first THEN Internal promise internal first THEN internal second THEN External second THENCopy the code

Fourth section of code

let p = new Promise((resolve, reject) = > {
  console.log("External promise");
  resolve();
})
p.then((a)= > {
    console.log("External first then");
    new Promise((resolve, reject) = > {
      console.log("Internal promise");
      resolve();
    })
      .then((a)= > {
        console.log("First inside then");
      })
      .then((a)= > {
        console.log("Internal second then");
      });
  })
p.then((a)= > {
    console.log("External second then");
  });
Copy the code

In this code, the external registration uses the non-chained call writing method, according to the above explanation, we know that the external code of P.hen is registered in parallel. So after the code is executed in the internal New Promise, the P.hen is synchronously registered.

After the first internal THEN is registered, the second external THEN is executed (both the second external THEN and the first external THEN are registered synchronously). Then execute the first internal THEN, and the second internal THEN in turn.

output:

External PROMISE External first THEN Internal promise external second THEN internal first THEN internal second THENCopy the code

I believe that after reading the above four pieces of code, I have a pretty good idea of how to execute and register a Promise.

If you still do not understand, please read several times, I believe you can understand ~~~~~~~~

Core ideas:

The registration microtask queue and execution of a Promise’s THEN are separate. Registration: is the execution of code that fully follows JS and Promise. Execution: first synchronization, then microtask, then macro task.

Only by understanding the above separately can you truly understand the order in which they are executed ~~~~~~~~~~~~~~~~

Fifth piece of code

After careful and in-depth text analysis above, I believe you will see the light. Here’s another topic for consolidation:

new Promise((resolve, reject) = > {
  console.log("External promise");
  resolve();
})
  .then((a)= > {
    console.log("External first then");
    new Promise((resolve, reject) = > {
      console.log("Internal promise");
      resolve();
    })
      .then((a)= > {
        console.log("First inside then");
      })
      .then((a)= > {
        console.log("Internal second then");
      });
    return new Promise((resolve, reject) = > {
      console.log("Internal promise2");
      resolve();
    })
      .then((a)= > {
        console.log("The first THEN2 inside.");
      })
      .then((a)= > {
        console.log("The second inside then2");
      });
  })
  .then((a)= > {
    console.log("External second then");
  });
Copy the code

This code is actually a combination of the first problem and the second problem. The second external then depends on the result of the internal return, so it waits for the return to complete. The first internal New Promise becomes an alternate output with the second internal New Promise, understood in the same way as the second code.

output:

External PROMISE External first THEN Internal Promise internal FIRST THEN internal first THEN2 internal second THEN internal second THEN2 external second THENCopy the code

The implementation difference between Promise/A+ and webKit kernel’s JS engine Promise

We know that ES6 promises need to be considered for backward compatibility. In development, promises of the system kernel are often not used, but NPM install Promise is used to introduce. Is the PROMISE’s JS implementation exactly the same as the browser’s?

In the resolve state, the Promise’s THEN is registered to the microtask queue only after the previous THEN has completed.

The difference here is that then returns a promise.resolve ();

new Promise((resolve, reject) = > {
  console.log('external promise');
  resolve();
})
  .then((a)= > {
    console.log('External first THEN');
    new Promise((resolve, reject) = > {
      console.log('internal promise');
      resolve();
    })
      .then((a)= > {
        console.log('First internal THEN');
        return Promise.resolve();
      })
      .then((a)= > {
        console.log('Internal second THEN');
      })
  })
  .then((a)= > {
    console.log('External second THEN');
  })
  .then((a)= > {
    console.log('External third THEN');
  })
Copy the code

Let’s ignore the internal first THEN return, and follow the above learning, the normal understanding, we can still be inside and outside the registration and run alternately.

output:

External PROMISE External first THEN Internal promise internal first THEN external second THEN Internal second THEN external third THENCopy the code

This problem execution sequence diagram:

Above we are the output of the code using the Promise’s JS implementation.

However, if you run this code in Chrome/Safari, the result is different. Here is the result from the WebKit kernel browser.

What is the reason for this? Return promise.resolve ();} To understand this, we need to distinguish understanding from registration and implementation.

Return promise.resolve () is encountered after executing the output “first internal THEN”; Let’s analyze the promise.resolve ();

Promise/A+ implementation:

Return promise.resolve () creates a Promise instance and sets the Promise instance to the resolve state. The promise.resolve () is synchronized and the Promise has been completed. Therefore, it will not affect the registration of other then. So our analysis above is completely correct. Here is the implementation of promise.resolve, which we found to be completely synchronized, so it doesn’t affect the final result.

Promise.resolve = function (value) {
  if (value instanceof Promise) return value;
  if (value === null) return NULL;
  if (value === undefined) return UNDEFINED;
  if (value === true) return TRUE;
  if (value === false) return FALSE;
  if (value === 0) return ZERO;
  if (value === ' ') return EMPTYSTRING;
  if (typeof value === 'object' || typeof value === 'function') {
    try {
      var then = value.then;
      if (typeof then === 'function') {
        return new Promise(then.bind(value)); }}catch (ex) {
      return new Promise(function (resolve, reject) { reject(ex); }); }}return valuePromise(value);
};
Copy the code

Implementation of the Promise browser (WebKit) :

Return promise.resolve (), create a Promise instance, and execute resolve, putting the value of the Promise’s resolve(undefined here) into the microtask queue. Change the Promise state to resolve. Then execute the “external second THEN”, then register the “external third THEN”, then execute the “internal first then” return resolve Promise of undefined value, and then execute the” internal first THEN “return resolve Promise. Then register the next “THEN”, but there is no next “THEN”, the execution is complete, the entire return task is complete, the current synchronization task is completed, and then the “external third then” is executed, and then the “external fourth THEN” is registered. At this point, the “internal first THEN” is executed, and the “internal second THEN” is registered. Finally, the “external fourth THEN” is executed, and the “internal second THEN” is executed.

The source code is as follows:

void Promise::Resolver::Resolve(Handle<Value> value) {
  i::Handle<i::JSObject> promise = Utils::OpenHandle(this);
  i::Isolate* isolate = promise->GetIsolate();
  LOG_API(isolate, "Promise::Resolver::Resolve");
  ENTER_V8(isolate);
  EXCEPTION_PREAMBLE(isolate);
  i::Handle<i::Object> argv[] = { promise, Utils::OpenHandle(*value) };
  has_pending_exception = i::Execution::Call(
      isolate,
      isolate->promise_resolve(),
      isolate->factory()->undefined_value(),
      arraysize(argv), argv,
      false).is_null();
  EXCEPTION_BAILOUT_CHECK(isolate, /* void */;) ; }Copy the code
PromiseResolve = function PromiseResolve(promise, x) {
    PromiseDone(promise, +1, x, promiseOnResolve)
}
function PromiseDone(promise, status, value, promiseQueue) {
    if (GET_PRIVATE(promise, promiseStatus) === 0) { PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status); PromiseSet(promise, status, value); }}Copy the code

With that in mind, if you add another “THEN” to the outer layer, then you know the result. After executing the “internal second THEN” that you just registered, you start executing the “external fifth THEN” that you just registered.

Consolidate the

Given the order you’ve learned to execute promises, you should be able to answer the following question. If not, consider reading another one.

new Promise((resolve, reject) = > {
  console.log("External promise");
  resolve();
})
.then((a)= > {
    console.log("External first then");
    new Promise((resolve, reject) = > {
        console.log("Internal promise");
        resolve();
    })
    .then((a)= > {
        console.log("First inside then");
    })
    .then((a)= > {
        console.log("Internal second then");
    });
    return new Promise((resolve, reject) = > {
        console.log("Internal promise2");
        resolve();
    })
    .then((a)= > {
        console.log("The first THEN2 inside.");
    })
    .then((a)= > {
        console.log("The second inside then2");
    });
})
.then((a)= > {
    console.log("External second then");
});
Copy the code

If you think this post is valuable to you, please like it and follow us on our website and our WecTeam account, where we will post quality articles every week: