Introduction of Promise

Promises first appeared as a solution to the callback hell of asynchronous behavior in programming. Before there is no Promise, for the asynchronous behavior of a function, the callback function is usually triggered at the end of a function call, which will lead to multiple levels of callback functions, which is difficult to maintain. A Promise has two parameters, a success callback and a failure callback, so outside of the Promise, the timing of success and failure can be accurately obtained, and the Promise supports chained calls, which are easy to make multiple calls, but are always single-level and easy to maintain.

Two asynchronous behaviors are compared

Callback to hell mode

var sayhello = function (name, callback) {
  setTimeout(function () {
    console.log(name);
    callback();
  }, 1000);
}
sayhello("first".function () {
  sayhello("second".function () {
    sayhello("third".function () {
      console.log("end");
    });
  });
});
// Output: first second third end
Copy the code

Promise to write

let sayHello = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    console.log("first");
    resolve();
  }, 1000);
});
sayHello
  .then(() = > {
    console.log("second");
  })
  .then(() = > {
    console.log("third");
  })
  .then(() = > {
    console.log("end");
  });
Copy the code

We can find that no matter how many times a Promise has logic processing, each time only has one layer, which is clearly visible, unlike callback, which is nested and difficult to clear up.

Promise based

Promise instance

let promise = new Promise(
  function(resolve, reject) { 	// Executor
  	setTimeout(() = >{		
    resolve("done"),1000);
});
Copy the code

We just need new Promise to create a Promise, and immediately call the implementer. Executor takes two arguments: resolve and reject. These are predefined by the JavaScript engine and don’t need to be created, we just need to call the corresponding method in the state we want to inform.

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("...")); / / is ignored
  setTimeout(() = > resolve("...")); / / is ignored
});
Copy the code

There can only be one result or one error and the executor can only call one resolve or one reject. Any state change is final. All other calls to resolve and reject are ignored and resolve/reject takes only one argument (or contains no arguments) and ignores additional arguments

Then we wonder why we went to so much trouble and did so much work inside a Promise to get him into a state, and what did his failure have to do with our previous callback hell?

State and Result are internal. The state and result properties of Promise objects are internal. We have no direct access to them. But we can use the.then/.catch/.finally method on them. We describe these methods below.Copy the code

Above we used the Promise to produce a success or failure result, which can be received for the consumption function by using the.then,.catch, and.finally methods.

then

let promise = new Promise(function(resolve, reject) {
  setTimeout(() = > resolve("done!"), 1000);
});

// resolve runs the first function in.then
promise.then(
  result= > alert(result), // "done!"
  error= > alert(error) / / is not running
);
Copy the code

The first argument to.then is a function that runs after promise Resolved and receives the result. The second argument to.then is also a function that will run after the promise rejected and receive an error. If we are only interested in successful completion, we can provide a function argument only for.then:

let promise = new Promise(resolve= > {
  setTimeout(() = > resolve("done!"));
}, 1000);

promise.then(alert); // "done!"
Copy the code

The.catch(f) call is a complete simulation of.then(null, f), which is just a shorthand. Simply put, a catch is a way of receiving the result of an error. If the promise is in the pending state, the.then/catch/finally handler will wait for it. Otherwise, if the promise is already settled, they will run the self-test case to write a 3s popover promise

function delay(ms) {
  // Your code
  return new Promise(resolve,reject){
  	setTimeout(resolve,3000)
  }
 }

delay(3000).then(() = > alert('runs after 3 seconds'));
Copy the code

catch

If the callback fails, then just set the first argument of the THEN to null or use a catch, which accepts the reject result

let promise = new Promise((resolve, reject) = > {
  setTimeout(() = > reject(new Error("Whoops!")), 1000);
});

//.catch(f) the same as promise.then(null, f)
promise.catch(alert); // Error: Whoops!

Copy the code

finally

Regardless of the outcome, the function in here will be executed. The **finally()** method returns a Promise. At the end of the promise, whether the result is fulfilled or rejected, the specified callback function is executed. This provides a way to execute code that needs to be executed after the Promise has completed successfully or not. This avoids the need for the same statement to be written once in both then() and catch().

Build a Promise

After I learned about the Promise, I became very interested in how to realize it, so I tried to build a Promise myself. First, we need to analyze our requirements, what we want to achieve, what functions we want to achieve, and determine our goals.

  1. We’re going to implement a class called Promise.
  2. Class to implement a resolve success notification.
  3. Class, we’re going to implement a Reject failure notification.
  4. Executor executes immediately.
  5. We also want to implement a then that gets the result.
  6. A catch that catches an error.
  7. A finally regardless of the result.

We started to realize our own Promise in two big steps according to the figure above.

First, construct the Promise class.

  1. In the initialization phase, we consider that a Promise has three states and two results. So we’re going to initialize the state and the result.
  2. Then when we send the success and failure messages, we change the state and save the results.
class Promise {
  constructor(executor) {
    if (typeofexecutor ! = ="function") {
      console.log("Parameter is not valid");
    } else {
      this.status = "pending";
      this.value = undefined;
      this.error = undefined;
      // Automatically run the functions in Promise
      try {
        // Give the resolve and reject functions to the user
        executor(resolve, reject);
      } catch (e) {
        // If an exception is thrown in a function, inject it into rejectreject(e); }}}resolve(data) {
    // Triggered successfully
    if (this.status === "pending") {
      this.status = "fulfilled";
      this.value = data; }}reject(data) {
    // Failure triggers
    if (this.status === "pending") {
      this.status = "rejected";
      this.error = data; }}then() {}
  catch() {}
  finally(){}}Copy the code

Now that we’ve achieved these goals, we’re going to implement a way to receive the resulting information.

  1. Then takes two arguments, the first of which will run after promise Resolved and receive the value. The second runs after the Promise reject and receives an error.
  2. A catch accepts only one function, which the promise runs and receives an error.
  3. Finally executes regardless of the result.
class Promise {
  constructor(executor) {
    if (typeofexecutor ! = ="function") {
      console.log("Parameter is not valid");
    } else {
      this.status = "pending";
      this.value = undefined;
      this.error = undefined;
      // Automatically run the functions in Promise
      try {
        // Give the resolve and reject functions to the user
        executor(this.resolve, this.reject);
      } catch (e) {
        // If an exception is thrown in a function, inject it into reject
        this.reject(e);
      }
    }
  }
  resolve = (data) = > {
    // Triggered successfully
    if (this.status === "pending") {
      this.status = "fulfilled";
      this.value = data; }}; reject =(data) = > {
    // Failure triggers
    if (this.status === "pending") {
      this.status = "rejected";
      this.error = data; }};then(onFulfilled, onRejected) {
    if (this.status === "fulfilled") {
      onFulfilled(this.value);
    }
    if (this.status === "rejected") {
      onRejected(this.error); }}catch(onRejected) {
    if (this.status === "rejected") {
      onRejected(this.error); }}finally(onFinally) {
    if (this.status ! = ="pending") { onFinally(); }}}Copy the code

In this way, we completed a simple version of the Promise. Let’s put the file to the test and see what happens.

<! DOCTYPE html><html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./index.js"></script>
    <script>
      let promise = new Promise((resolve, reject) = > {
        resolve("coolFish!");
      });
      promise.then(
        (data) = > {
          console.log("Success" + data);
        },
        (data) = > {
          console.log("Failure"+ data); });</script>
  </body>
</html>


Copy the code

The result, like the Promise, can implement different actions for success and failure, and we’ll start extending its functionality, starting with support for chained calls.

Promise.then

This is very depressing. First of all, we define the promise. Then (onFulfilled, onRejected)

  1. This will be fulfilled if onFulfilled or onRejected is not a function.
  2. Create and return a PROMISE instance.
  3. Add onFulfilled and onRejected to the event queue (depending on the state of the promise).
  4. The state is fulfilled. OnFulfilled.
  5. If the state is Rejected, perform onRejected.
  6. If no decision is made, it is added to the event queue.
  then(onFulfilled, onRejected) {
    // Create and return a Promise instance
    return new Promise((resolve, reject) = > {
      let wrapOnFulfilled = () = > {
        setTimeout(() = > {
          try {
            console.log("wrapOnFulfilled");
            let x = onFulfilled(this.value);
            resolve(x);
          } catch(error) { reject(error); }},0);
      };
      let wrapOnRejected = () = > {
        setTimeout(() = > {
          try {
            console.log("wrapOnRejected");
            let x = onRejected(this.error);
            resolve(x);
          } catch(error) { reject(error); }},0);
      };
      if (this.status === "fulfilled") {
        wrapOnFulfilled();
      } else if (this.status === "rejected") {
        wrapOnRejected();
      } else {
        this.onFulfilledCallbacks.push(wrapOnFulfilled);
        this.onRejectedCallbacks.push(wrapOnRejected); }}); }Copy the code

Promise.all

All takes an array of Promises as an argument (technically, it can be anything iterable, but it’s usually an array) and returns a new Promise. When all the given promises are settled, the new promise will resolve, and the resulting array will become the results of the new promise. Let’s look at a flowchart and then follow the flowchart to implement our code

all(promises) {
    return new Promise((resolve, reject) = > {
      // If promise. all receives an empty array ([]), it will decide immediately.
      if(! promises.length) { resolve([]); }let result = [];
      let resolvedPro = 0;
      for (let index = 0, length = promises.length; index < length; index++) {
        Promise.resolve(promises[index]).then(
          (data) = > {
            // Use index instead of push. This is because you want to keep the return value consistent with the location of the promise you received.
            result[index] = data;
            if(++resolvedPro === length) { resolve(result); }},(error) = >{ reject(error); }); }}); }Copy the code

Promise.race

// Note that if promise.race receives an empty array ([]), it will be suspended instead of resolved immediately.
Promise.race = function(promises) {
  return new Promise((resolve, reject) = > {
    promises.forEach((promise) = > {
      Promise.resolve(promise).then(resolve, reject);
    });
  });
};

Copy the code

Promise.allSettled

This will be fulfilled someday. // allSettled will return a Promise which has been fulfilled after all the given promises have been fulfilled.
// With an array of objects, each representing the corresponding promise result.
Promise.allSettled = function(promises) {
  return new Promise((resolve, reject) = > {
    if(! promises.length) { resolve([]); }let result = [];
    let resolvedPro = 0;
    for (let index = 0, length = promises.length; index < length; index++) {
      Promise.resolve(promises[index])
        .then((data) = > {
          // Use index instead of push. This is because you want to keep the return value consistent with the location of the promise you received.
          result[index] = {
            status: FULFILLED_STATE,
            value: data,
          };
          if (++resolvedPro === length) {
            resolve(result);
          }
        })
        .catch((error) = > {
          result[index] = {
            status: REJECTED_STATE,
            reason: error,
          };
          if(++resolvedPro === length) { resolve(result); }}); }}); };Copy the code