One top gaming engineer, who wished to remain anonymous, once said that the best way to learn Promise was to read its spec definition first. So where can I find the standard definition of Promise?

The answer is Promises/A+.

Assuming you’ve opened the above specification definition page and tried to start reading it (don’t close it just because it’s In English, trust yourself that you can), the specification begins by describing the definition of promises, how to interact with them, and then emphasizes the stability of the specification. In terms of stability, in other words: We may revise the specification, but the changes are minimal and backward compatible, so learn that this is the definitive standard, and go back to Google in 50 years, and the specification will still be 😂.

Ok, let’s go back to the specification. From the introduction, what is Promise?

A promise represents the eventual result of an asynchronous operation.

A Promise represents the end result of an asynchronous operation.

Highlight!! This actually leads to JavaScript’s motivation for introducing promises: asynchrony.

The best way to learn a new technology is to understand how it was created and what problems it solves. What does Promise have to do with asynchronous programming? What problem did Promise solve?

To answer these questions, we need to recall what was wrong with asynchronous programming before promises?

Asynchronous programming

Asynchronous JavaScript programming is closely related to the browser event loop. There are many articles and columns on the web that describe the browser event loop mechanism. If you don’t already know about it, you can read the following article.

  • “Front-end advancement” from multi-threading to Event Loop comprehensive combing
  • What the heck is the event loop anyway?
  • JSConf.Asia, Event Loop

Now that you know about event loops, what are the problems with asynchronous programming?

Due to the single-threaded architecture of Web pages, the asynchronous programming model of JavaScript is based on Message queues and Event loops, as shown below.

The callback function of our asynchronous task will be put into the message queue, and then wait for the synchronous task on the main thread to complete execution. When the execution stack is empty, the event loop mechanism will schedule the synchronous task into the execution stack to continue execution.

This leads to one of the hallmarks of asynchronous JavaScript programming: asynchronous callbacks, like network requests,

// Successful asynchronous callback function
function resolve(response) {
  console.log(response);
}
// Failed asynchronous callback function
function reject(error) {
  console.log(error);
}

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = () = > resolve(xhr.response);
xhr.ontimeout = (e) = > reject(e);
xhr.onerror = (e) = > reject(e);

xhr.open("Get"."http://xxx");
xhr.send();
Copy the code

While asynchronous callbacks can be made elegant by simple encapsulation, for example,

$.ajax({
  url: "https://xxx".method: "GET".fail: () = > {},
  success: () = >{}});Copy the code

But there is still no way to solve the problem of “callback hell” after business complexity, such as multiple dependency requests,

$.ajax({
  success: function (res1) {
    $.ajax({
      success: function (res2) {
        $.ajax({
          success: function (res3) {
            // do something...}}); }}); }});Copy the code

This linear nesting of callbacks makes asynchronous code difficult to understand and maintain, as well as mentally taxing. So we needed a technique to solve the problem of asynchronous programming style, and that was what motivated Promise.

Understanding the Promise background and motivation helps us understand the specification, so let’s go back to the definition of the specification.

specification

The Promise A+ specification begins by defining some of the terms and states associated with A Promise.

Terminology, the term

  1. “Promise,” a possessionthenMethod that behaves in accordance with this specification
  2. “Thenable”, a definitionthenThe object or function of a method
  3. “Value”, any JavaScript legal value (includingundefined.thenablepromise)
  4. “The exception”, the use ofthrowA value thrown by the statement
  5. Reason: indicates onepromiseReasons for rejection

State of the State,

The current state of a promise must be one of the following three states: Pending, depressing, and Rejected

  • When a promise is Pending, it can be migrated to Fullfilled or Rejected
  • During this period, the promise must have an immutable final value and cannot be transferred to other states
  • When in Rejected, the promise must have an immutable rejection reason and cannot be migrated to another state

So Promise internally maintains a state machine like the one shown below,

A Promise can be Fulfilled or Rejected when it is created, which is a big pity or a pity. This process is irreversible.

After defining related terms and states, it is a detailed description of the execution of the THEN method.

Then

A promise must provide a THEN method to access its current value, final value, and reason for rejection.

The then method takes two arguments,

promise.then(onFulfilled, onRejected);
Copy the code
  • OnFulfilled, which is called after the promise execution ends. The first parameter is the final value of the promise
  • OnRejected: is called after a promise is rejected. The first parameter is the promise rejection reason

There are some rules for calling and returning values for these two callback arguments and then,

  1. OnFulfilled and onRejected are both optional parameters.

  2. Ondepressing and onRejected must be called as functions, and this will be called with default binding rule, that is, in strict environment, this equals undefined, and in non-strict mode, this is a global object (window in browser). If you don’t know the binding rules for this, please refer to my previous article “Probably the best this resolution…” It’s very detailed.

  3. OnFulfilled and onRejected can only be called if the execution environment stack contains only platform code. Since the promise implementation code is itself platform code (JavaScript), this rule can be interpreted as ensuring that the two callbacks are executed asynchronously after the event loop in which the THEN method is called. Isn’t that the order of microtasks? So the implementation principle of promise is based on the microtask queue.

  4. The THEN method can be called multiple times by the same promise, and all successful or rejected callbacks need to be called in the order in which they were registered. So the promise implementation needs to support chain calls, so let’s think about how to support chain calls, and we’ll have the implementation later.

  5. The then method must return a Promise object.

For point 5, there are several extended definitions that combine the return value with the promise resolution process,

promise2 = promise1.then(onFulfilled, onRejected);
Copy the code

The two callback arguments to then may throw an exception or return a value,

5.1 If ondepressing or onRejected throws an exception E, then the returned promise2 must refuse to execute and return the reason for the rejection.

5.2 If onFulfilled or onRejected returns a value X, the promise will be implemented

  • ifxIt’s the same thing as the return promise2, which is promise2 and promise2xTo point to the same objectTypeErrorRefuse to implement promise2 as a reason for denial
  • ifxPromise will judgexIn the state. If the state is waiting, hold. If it is in the executing state, execute promise2 with the same value. If the state is rejected, reject promise2 for the same reason
  • ifxIs an object or a function that willx.thenAssigned tothen; If you takex.thenThrows an error when the value ofe, theeReject promise2 for rejection reasons. ifthenTheta is a function of thetaxfunctionallythisAnd pass in two callback functions resolvePromise, which are called as arguments to the rejectPromise function

Reading this, I’m sure you’re just as eager as I am to make a Promise. Now that we know the principles and definitions, let’s write a Promise.

Write a Promise

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

function resolve(value) {
  return value;
}

function reject(err) {
  throw err;
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")); }let called;
  if ((typeof x === "object"&& x ! =null) | |typeof x === "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) = > {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) = > {
            if (called) return;
            called = true; reject(r); }); }else{ resolve(x); }}catch (e) {
      if (called) return;
      called = true; reject(e); }}else{ resolve(x); }}class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.resolveCallbacks = [];
    this.rejectCallbacks = [];

    let resolve = (value) = > {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.resolveCallbacks.forEach((fn) = >fn()); }};let reject = (reason) = > {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.rejectCallbacks.forEach((fn) = >fn()); }};try {
      executor(resolve, reject);
    } catch(error) { reject(error); }}then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : resolve;
    onRejected = typeof onRejected === "function" ? onRejected : reject;
    let promise2 = new Promise((resolve, reject) = > {
      if (this.status === FULFILLED) {
        setTimeout(() = > {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      }

      if (this.status === REJECTED) {
        setTimeout(() = > {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      }

      if (this.status === PENDING) {
        this.resolveCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0);
        });

        this.rejectCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0); }); }});returnpromise2; }}Copy the code

summary

Starting from the Promise A+ specification, we first explored the background and motivation of the birth of Promise, learned the development history of asynchronous programming, and then went back to the specification to read the definition of related terms, state and execution process, and finally tried the simple version of Promise implementation. The latest edition of JavaScript Advanced Programming (Version 4) translates Promise as a “Promise” as a solution for modern JavaScript asynchronous programming, Promise solved the problem of multiple layers of nesting with techniques such as callback function delay binding, callback function return value penetration, and error “bubbling,” standardizing uniform processing of the results (success or failure) of asynchronous tasks.

Refer to the link

  • Promises/A+ Spec
  • Message queues and event loops: How do pages come alive?
  • Promise – JavaScript | MDN
  • ECMAScript® 2022 Language Specification – TC39
  • Interviewer: “Can you make a Promise by hand?”

Write in the last

This article first in my blog, uneducated, unavoidably have mistakes, the article is wrong also hope not hesitate to correct!

If you have any questions or find errors, you can ask questions or correct errors in the corresponding issues

If you like or are inspired by it, welcome star and encourage the author

(after)