preface

Last time we started with higher-order functions and talked about promises. This time we’ll talk about:

  • frompromise A+The specification andpromiseApplication resultspromiseThe characteristics of the
  • promiseRelationship to EventLoop

Understand promise from Erwin Schrodinger’s Cat

Schrodinger’s cat was a thought experiment proposed by the famous Austrian physicist Schrodinger, so what does this have to do with promise? In this famous experiment, we assumed that there would be a cat in the box, and then we opened the box and only two things happened, the cat was dead or alive:

This is also A pity. According to the Promise A+ specification, when A promise is created, it has three possible states: Pending/depressing/Rejected. If we broaden the scope A bit, This is a big pity/Rejected can also be called Settled:

Okay, I’m sure you understand the three states of promise. If you are careful, you may not understand the methods like then() and catch(). Let’s go back to the cat example. But he didn’t end the experiment. Instead, he dealt with both conditions and continued the experiment:

Similarly, a complete promise can only be Fulfilled in two cases when the Pending state changes, namely Fulfilled and Rejected. Moreover, we can see that the arrow is one-way, which means that the process is irreversible.

This means that when the Pending state changes, no matter it becomes a pity or Rejected, it cannot be changed again.

In both cases, we can pass in then() two callback functions, onFulfillment and onRejection, to handle the different cases.

As you can see, when onFulfillment is done, we usually do something asynchronous and onRejection is usually error handling. We then return the current promise until his THEN () is executed again the next time.

A promise.then().then().then().then() is what we called a chain call in our last article.

Look at features through the implementation of promises

From the previous section, we know several features of promise itself:

  • promiseThere are three states:Pending(The initial value ispending) /Fulfilled / Rejected.
  • promiseThe change of state is irreversible:Pending -> FulfilledorPending -> Rejected
  • promisesupportthen()The chain call to.

But there are some other features that we need to analyze from a code perspective.

1. After the vm is created, execute it immediately

I promise something in advance that I will do in the future.

Therefore, some students will think that the function received by the constructor will be executed only when the promise is Fulfilled, that is, when the state of the promise changes from Pending to pity or Rejected.

But in practice, when a promise is created, its constructor receives a function that executes immediately, even if we don’t define then() :

let p = new Promise((resolve, reject) = > {
  console.log('A new promise was created1')
  console.log('A new promise was created2')
  console.log('A new promise was created3')
  setTimeout((a)= > {
    console.log('log setTimeout')},3000)
  resolve('success')})console.log('log outside')
Copy the code

Output result:

A new promise was created1
A new promise was created2
A new promise was created3
log outside
log setTimeout
Copy the code

2. Exception handling methods

According to the Promise A+ specification, the promise’s then() takes two arguments:

promise.then(onFulfilled, onRejected)
Copy the code

OnFulfilled is called after the execution of onFulfilled and onRejected is called after the execution of onFulfilled.

let p = new Promise((resolve, reject) = > {
  reject('reject')
  //throw 'error'
})

p.then(
  data= > {
    console.log('1', data)
  },
  reason => {
    console.log('reason:', reason)
  }
)
Copy the code

The last print was:

reason: reject
Copy the code

It works, doesn’t it? But we find that in practice, we don’t define then() like this:

p.then(
  data= > {
    console.log('1', data)
  },
  reason => {
    console.log('reason1:', reason)
  }
).then(
  data= > {
    console.log('2:', data)
  },
  reason => {
    console.log('reason2:', reason)
  }
).then(
  data= > {
    console.log('3:, data)
  },
  reason => {
    console.log('reason3:', reason)
  }
)
Copy the code

This is a big pity. Instead, I will use “catch” with onFulfilled:

p.then(data= > {
  console.log('1', data)
}).then(data= > {
    console.log('2:', data)
  }).then(data= > {
    console.log('3:, data)
  }).catch(e= > {
      console.log('e2:', e)
    })
Copy the code

On the surface, the effect is the same, so what good is that?

  1. Reduce code.
  2. inonFulfilled()If an error occurs, it will be caught without interrupting the execution of the code.

3. Then () is executed asynchronously

Take a look at this code:

let p = new Promise((resolve, reject) = > {
  console.log('A new promise was created1')
  console.log('A new promise was created2')
  console.log('A new promise was created3')
  resolve('success')})console.log('log outside')

p.then(data= > {
  console.log('then:', data)
})
Copy the code

Execution Result:

A new promise was created1
A new promise was created2
A new promise was created3
log outside
then: success
Copy the code

We can clearly see that the content printed in then() is last. Why is this? This is because functions passed in p.chen () are pushed into microtasks(a type of asynchronous task queue), which are processed after executing the code in the stack (synchronous tasks).

The following code is handled in the synchronization task:

console.log('A new promise was created1')
console.log('A new promise was created2')
console.log('A new promise was created3')
console.log('log outside')
Copy the code

Okay you may have some questions about this, for example:

  • What is theSynchronization task ?
  • What is theExecution stack?
  • What is themicrotasks?
  • What is theAsynchronous task queue?

To understand this, you have to talk about Event Loop.

What is an Event loop? Why do we need Event loop?

A description of this can be found in the W3C documentation:

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.

Translation:

The client must use the event loop described in this section to coordinate events, user interactions, scripting, rendering, networking, and so on. There are two types of event loops: event loops for browsing context and event loops for workers.

We can write a piece of JavaScript code, open the page in a browser, or run it in a Node environment, and get the desired result, but how does this code work?

As many of you know, JavaScript engines execute code, and JavaScript engines rely on a host environment, and the most common JavaScript host environment is the browser.

What does this have to do with EventLoop?

Because the host environment is the browser, the JavaScript engine is designed to be single-threaded.

Why can’t it be multithreaded? For example, what if we had two threads working on the same DOM element? Isn’t it.

Okay, since it’s single-threaded, that means we can only execute code sequentially, but if we execute a line that’s particularly time consuming, is anything after that line blocked?

So we need to implement asynchrony in a single-threaded engine, and the Event loop is the key.

Task queue & macro task & micro task in Event loop

First, when a piece of code is given to a JavaScript engine, it distinguishes whether the code is synchronous or asynchronous:

  • Synchronized code is executed in the main thread
  • Asynchronous code is added to the task queue, waiting for the main thread to tell it to execute

Asynchronous code is added to task queues, which are divided into macro tasks and micro tasks.

A browser context may have multiple macro task queues but only one microtask queue. This is what you might expect:

But in reality, each macro task contains a queue of microtasks:

So the question is, how do we determine whether this code is going to be added to the macro task queue or the micro task queue?

Let’s take a look at the document:

Each task is defined as coming from a specific task source. All the tasks from one particular task source and destined to a particular event loop

Each task is defined by a special task source. All tasks from the same particular task source are sent to a particular event loop

Therefore, we can classify tasks according to different sources, and tasks from different sources are corresponding to different task queues

  • (Macro-task)I/O.setTimeout + setInterval + setImmediate.UI renderder
  • (Micro-task)Promiseprocess.nextTickMutationObserver.Object.observe

With these concepts in mind, let’s take a look at the full implementation.

Event loop Indicates the complete execution process

The following figure is a reference to Philip Roberts’ PPT for further elaboration:

The order of the graph is viewed from the top down:

  1. The code starts executing,JavaScriptThe engine differentiates all code.
  2. Synchronous code is pushed onto the stack, and asynchronous code is added to the end of the macro task queue, or the end of the microtask queue, depending on the source.
  3. Wait for the code in the stack to finish executing, then notify the task queue to execute the macro task at the head of the queue.
  4. After the macro task is executed, the associated microtask starts to be executed.
  5. After the associated microtask is executed, the next macro task is executed until all macro tasks in the task queue are executed.
  6. Execute the next task queue.

Steps 3-4-5 are the basics of an event loop.

The last

I wonder if this article has made you fully understand? Leave a comment with any thoughts or suggestions

Small volumes of Chrome debugging tips you didn’t know about are now available for pre-order.

Welcome to pay attention to the public number “front-end bully”, scan code attention, good goods waiting for you ~