• Five pieces of code deeply analyze the registration microtask and code execution process of Promise
  • Analyze the implementation differences between Promise/A+ and WebKit’s Promise (chrome and Safari kernel)
  • Let’s solidify it. Start the problem

Promise is too familiar, but here is not to cover the superficial simple knowledge that everyone knows, but together with the Promise registration micro-tasks and implementation of the complete process. Be able to use Promise correctly and make sure that you know what Promise is

We usually learn Promises based on Promises/A+. But I have to tell you that this article will also examine the implementation differences between this JS implementation and webKit’s Promise. Specific to the difference in code execution.

The whole idea of this paper adopts code examples + analysis and explanation to interpret, so that everyone can understand, everyone can understand the purpose.

No exaggeration, if all read this article, then the Promise registration and implementation process will be invincible, deep in your bone marrow, you are the Promise god! ~ ~ ~ ~ ~ ~ ~

preface

This article has been interpreted in code to learn the whole process. Five pieces of code are provided here. If you can understand clearly and say the output process correctly, then you are as powerful as you. I give you a thumbs-up here to congratulate you on your thorough understanding of the implementation process of 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 title. Of course, if it’s a rookie like me, then let’s take a look

Before you read the answer, silently calculate the output yourself.

First 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("Inside the first THEN");
    })
    .then((a)= > {
    console.log("Internal second THEN");
    });
  })
  .then((a)= > {
    console.log("External second THEN");
  });

Copy the code

The output is relatively simple: execute the first external New Promise, execute resolve, and then execute the first external THEN. The outer first THEN method returns a Promise, which means that the execution of the outer second THEN will wait for the result of the return. Of course you execute the inner two THEN, and then you execute the outer second THEN.

Output: External promise External first THEN internal Promise internal first then internal second then external second then

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("Inside the first 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 how do we understand this?

When the then callback function is registered, we know that the event mechanism is “register first, execute first”, i.e. the “queue” mode of data structure, first in first out. So let’s see who signed up first.

The registration of the external second THEN needs to wait for the synchronization code execution of the external first THEN to complete. When an internal new Promise is executed and then a resolve is encountered, the resolve execution is completed, indicating that the state of the Promise has been reversed, and then the registration of the first internal. Then micro-task is started, and the synchronization execution is completed. 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 first, then 222. Because the output of 222 is the execution of the microtask, 111 is the synchronous execution.

Resolve (‘ resolve ‘); then (‘ resolve ‘); then (‘ resolve ‘); then (‘ resolve ‘);

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

At this point, the synchronization of the first external THEN has been completed, and the registration of the second external THEN has been started, at which point all external synchronization tasks have been completed. After the synchronization is complete, then the micro-task is executed. We find that the internal first THEN is registered before the external second THEN, so we will execute the internal first THEN and then register the internal second 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 then

We find that, obviously, one THEN is executed and the next THEN after that then is registered. According to the principle of task queue, we can find that internal and external THEN are executed alternately and then registered alternately. That’s why the output alternates between inside and outside. In addition, when I say then registration, I mean the registration of the microtask queue, not the execution of the.then method. In fact, the execution of the.then method can be understood as just initialization. If you look at the source code, you will know that the execution of. Then is indeed synchronous. Internally, a new Promise is opened, but since the previous state has not been transferred, the then will not be registered in the microtask queue at this time, but will wait for the completion of the previous execution. Therefore, it is ok to interpret.then unregistered microtasks as not yet executed.

Let’s look at the third 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("Inside the first 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 internal Promise code is written differently, instead of as a chain call.

How do I understand it here?

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

The main differences between the two methods are:

  • The registration of a chain call is context-dependent such as the registration of the external second THEN above, which requires the execution of the external first THEN to complete.
  • The way variables are defined, registrations are all synchronized like p.teng here and var p = new Promise are all synchronized.

The external second THEN is executed after the internal one is executed (because it takes precedence over the registration of the external second THEN) :

Output: External promise External first THEN internal Promise internal first then internal second then external second then

The fourth 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("Inside the first 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-chain call writing method, according to the above explanation, we know that the external code of P.chen is parallel synchronous registration. So as soon as the code internally executes the new Promise, p.Teng is all synchronized.

After the first internal THEN is registered, the second external THEN is executed (the second external THEN and the first external THEN are registered synchronously). And then the first internal THEN, and then the second internal THEN.

Output: External promise External first then internal Promise external second then internal first then internal second then

I believe that if I can understand the above four pieces of code, I have a good understanding of Promise implementation and registration.

If still not quite understand, trouble to see several times, I believe you will be able to understand ~~~~~~~~

Core ideas:

The registration microtask queue for Promise’s THEN is separated from execution. Registration: is the execution of code that fully complies with JS and Promise. Execution: Synchronization first, then micro task, then macro task.

Only by understanding the above separately can we truly understand their execution order ~~~~~~~~~~~~~~~~

The fifth code

After the above careful depth of text analysis, I believe you will suddenly see the light. One more consolidation topic:

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("Inside the first 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 THEN2 inside");
      });
  })
  .then((a)= > {
    console.log("External second THEN");
  });
Copy the code

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

Output: external promise external first then internal promise internal promise2 Internal first then internal first THEN2 internal second then internal second then2 external second then

Promise/A+ and WebKit Promise implementation differences

As we know, ES6 promises need to be considered for downward compatibility, and NPM install Promise is often used to introduce them instead of system kernel promises in development. So is the JS implementation of Promise exactly the same as the browser implementation?

According to the analysis of the above four codes, we understand that the execution of Promise’s THEN depends on the completion of the execution of the previous THEN, that is, the resolve state, before it starts to register in the microtask queue.

Resolve (); then returns 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('Internal first 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

We ignore the first internal then return, according to the above learning, we can conclude that it is still inside and outside alternately register and run.

Output: External promise External first then internal Promise internal first then external second then internal second then external third then

Execution sequence diagram of this problem:

Above we show the output of code that uses Promise’s JS implementation.

However, if you run this code on Chrome/Safari, the result is different. Here is what the WebKit kernel looks like.

What is the cause of this? Resolve () return promise.resolve () To understand this, we need to distinguish between registration and execution.

After executing the output “internal first then”, the return promise.resolve () is encountered; Resolve ();

Implementation of Promise/A+ :

Execute return promise.resolve () to create a Promise instance and set the Promise instance to the resolve state. The promise.resolve () is synchronized and the Promise has completed. Therefore, it will not affect the registration of other THEN. So the above analysis is completely correct. The following implementation of promise.resolve, as we found, is completely synchronous, so it does not 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

Promise’s browser (WebKit) implementation:

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

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 a “then” to the outer layer, then you know the result. Execute the “second internal THEN” that you just registered, and then execute the “fifth external THEN” that you registered.

Consolidate the

You should be able to answer the following question based on the sequence of promises you’ve learned above, but if not, you might want to look at it again.

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("Inside the first 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 THEN2 inside");
    });
})
.then((a)= > {
    console.log("External second THEN");
});
Copy the code

Welcome to follow my wechat official account: