I recently read some articles about the internal implementation of the Promise keyword in javascript, and tried to implement the Promise myself. Here are some relevant notes.

A Promise is an object that can return the result of an asynchronous operation at some point in the future. This can be the result of successfully resolved resolved or the cause of an error during the ResovLED resolution process. It has three states during execution:

  • This will be Fulfilled and the result state will be correctly resolved (call resolve())
  • Reject complete but result parse error status (call reject())
  • Pending is neither complete nor rejected

Internally, promises manage the three life cycle transitions by defining a set of state machines, so the first step is to define the basic structure of the state machine



function PromiseDemo() { this.PENDING = 0; this.FULFILLED = 1; this.REJECTED = 2; this.handlers = []; this.state = this.PENDING; // Initialize the state of the Promise. The initial state is PENDING this.value = null; // This is a big pity; // This is a big pity; // This is a big pity; // This is a big pity; // This is a big pity; this.value = result; }; const rejected = (error) => { this.state = this.REJECTED; this.value = error; }; }Copy the code

Now that the Promise’s state machine is defined, a Promise is in the completed state (resolve or Rejected) if its state is not pending. Once the state of a Promise has been switched from pending (resolve or Reject), it will not be changed, and a call to resolve or Reject will have no effect. This stability in the completed state is an important feature of promises.

The standard Promise is defined by Promises/A+ Specification

) community-made specification, a brief summary of the following rules that Promise implementation should follow:

  • A Promise is one that can provide compliance with standards.then()Method object
  • A pending Promise can be fulfilled or rejected
  • This is a big pity or the Promise of the rejected state cannot go to any other state after it is fulfilled
  • Once a Promise is fulfilled, it must have a value (possibly undefined) that cannot be changed

According to these principles, when defining the Promise state machine, three states are defined, which will be fulfilled and rejected () and rejection ().

Now that we have a method for transitioning the Promise state, where does it change for the caller? Let’s start with an example of using promises

const wait= () = > {return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('hello'); }, 0)})}wait().then((result) => { 
  console.log(result);  // hello
})
Copy the code

Wait functions have asynchronous operations such as setTimeout, wrapped with a Promise, and then called resolve or Reject when the asynchronous operation is finished. So the state change is triggered by the callback passed in when the Promise is instantiated. Now define resolve and reject for the incoming callback

/** *@params {fn} promise instantiates the incoming callback to receive resolve and reject */functionPromiseDemo(fn) { this.PENDING = 0; this.FULFILLED = 1; this.REJECTED = 2; this.handlers = []; This. State = PENDING; // Initialize the state of the Promise. The initial state is PENDING this.value = null; // This is a big pity /** * then() this is a big pity and onRejected // Const handler = (handle) => {// This is a big pity.if (this.state === this.PENDING) {
      this.handlers.push(handle);
    } else {
      if (
        this.state === this.FULFILLED &&
        typeof handle.onFulfilled === "function"
      ) {
        handle.onFulfilled(this.value);
      }
      if (
        this.state === this.REJECTED &&
        typeof handle.onRejected === "function") { handle.onRejected(this.value); }}}; const fulfilled = (result) => { this.state = this.FULFILLED; this.value = result; this.handlers.forEach(handler); this.handlers = null; }; const rejected = (error) => { this.state = this.REJECTED; this.value = error; this.handlers.forEach(handler); this.handlers = null; };functionResolve (value) {// Fulfill if errors occur during the fulfill process, you need to switch to the REJECTED state try {fulfilled(value); }catch(err) { rejected(err); This will be a pity (result); this will be a pity (result); this will be a pity (result); } catch (error) { rejected(error); } }, (error) => { rejected(error); }) this. Done = (onFulfilled, onRejected) => {// Make sure that the result processing operation and the asynchronous operation inside the Promise remain asynchronoussetTimeout(() => {
    handler({ onFulfilled,onRejected })
}, 0)
}
Copy the code

SetTimeout is used here to ensure that all operations within the Promise are asynchronous, that is, if we pass in no asynchronous operation within the Promise, we can guarantee that it will be output asynchronously, but generally we will not use the Promise if there is no asynchronous operation

The.done method implemented here is primarily used for the.then method. .then does the same thing as.done, printing the result of an asynchronous operation, except that.then re-constructs a Promise in the process when it executes. We usually call.then

promise.then( onFulfilled? : Function, onRejected? : Function ) => PromiseCopy the code

Promises/A+ To make Promises/A+ real,.then follows these rules:

  • onFulfilled()andonRejected()This parameter is optional
  • ifonFulfilledoronRejected()Not functions, they’re going to be ignored
  • onFulfilled()Will be called when the Promise state is fulfilled, and Promsie will be used asynchronouslyvalueAs its first parameter, it cannot be called before the Promise has transitioned to the depressing state
  • onRejected()Is called when the Promise state is Rejected, and takes the exception reason for the transition to Rejected as the first argument. It cannot be called before the Promise has transitioned to the Rejected state
  • onFulfilled()andonRejected()Cannot be called more than once
  • .then()This Promise can be invoked many times in a Promise. When the Promise is fulfilled, all of its own will be fulfilledonFullfilled()Callbacks must all follow them in.thenCall the sequential execution inside. Also when the Promise is in the Rejected state, all its ownonRejected()Callbacks must follow them in.thenCall the sequential execution inside
  • .then()You have to return a Promise
    Promise2 = Promise1.then(onFulfilled, onRejected)
    Copy the code
    • ifonFulfilled()oronRejected()Returns axValues, andxIt’s a Promise, then Promise2 will make peacexAnd the state ofvalueBe consistent, otherwise Promise2 willxThe value of switches to the depressing state
    • ifonFulfilled()oronRejected()Throw an exceptionePromise2 must transition to the Rejected state and be usedeAs a reason to
    • ifonFulfilled()Is not a function and promise1 transitions to the depressing state, promise2 must use the same values as promise1 to transition to the depressing state
  • ifonRejected()Promise1 is not a function and transitions to the Rejected state, promise2 must use the same exception reason as promise1 to transition to the Rejected state

Follow the.then() principle above and implement it briefly.

this.then = (onFulfilled, onRejected) => {
  return new Promise((resolve, reject) => {
      this.done((result) => {
        if(typeof onFulfilled === 'function'This is a big pity (result); // This is a big pity (pity); // This is a big pity (pity); }catch(err) { reject(err); }}else {
         resolve(result);
      }
    }, (err) => {
        if(typeof onRejected === 'function') { try { resolve(onRejected(err)); }catch(ex) { reject(ex); }}else{ reject(err); }})})}Copy the code

A new asynchronous operation will be wrapped in.then, and the result of that asynchronous operation will be received in the next.then(). Which is the penultimate of the.then() principle mentioned above. First we need to wrap the fn() call and add a helper function to getThen() to get the asynchronous operations in.then()

/** / const getThen = (value) => {const t = typeof value;if (t && (t === "object" || t === "function")) {
      const then = value.then;
      if (typeof then= = ="function") {
        console.log('functionThen'.then);
        return then; }}return null;
  };

const doResolve = (fn, onFulfilled, onRejected) => { try { fn( (result) => { try { onFulfilled(result); } catch (error) { onRejected(error); } }, (error) => { onRejected(error); }); } catch (error) { onRejected(error); }}; const resolve = (result) => { try { constthen = getThen(result);
      if (then) {
        doResolve(then, resolve, rejected);
        return; } fulfilled(result); } catch (error) { rejected(error); }};Copy the code

Complete code implementation:

function MyPromise(fn) {
  this.PENDING = 0;
  this.FULFILLED = 1;
  this.REJECTED = 2;
  this.handlers = [];

  this.state = this.PENDING;
  this.value = null;

  const handler = (handle) => {
    if (this.state === this.PENDING) {
      this.handlers.push(handle);
    } else {
      if (
        this.state === this.FULFILLED &&
        typeof handle.onFulfilled === "function"
      ) {
        handle.onFulfilled(this.value);
      }
      if (
        this.state === this.REJECTED &&
        typeof handle.onRejected === "function") { handle.onRejected(this.value); }}}; const fulfilled = (result) => { this.state = this.FULFILLED; this.value = result; this.handlers.forEach(handler); this.handlers = null; }; const rejected = (error) => { this.state = this.REJECTED; this.value = error; this.handlers.forEach(handler); this.handlers = null; }; /** / const getThen = (value) => {const t = typeof value;if (t && (t === "object" || t === "function")) {
      const then = value.then;
      if (typeof then= = ="function") {
        console.log('functionThen'.then);
        return then; }}return null;
  };

  const doResolve = (fn, onFulfilled, onRejected) => { try { fn( (result) => { try { onFulfilled(result); } catch (error) { onRejected(error); } }, (error) => { onRejected(error); }); } catch (error) { onRejected(error); }}; const resolve = (result) => { try { constthen = getThen(result);
      if (then) {
        doResolve(then, resolve, rejected);
        return; } fulfilled(result); } catch (error) { rejected(error); }};doResolve(fn, resolve, rejected); This. Done = (onFulfilled, onRejected) => {// Make sure that the internal operations are asynchronoussetTimeout(() => {
      handler({ onFulfilled, onRejected });
    }, 0);
  };

  this.then = (onFulfilled, onRejected) => {
    return new MyPromise((resolve, reject) => {
      return this.done(
        (value) => {
          if (typeof onFulfilled === "function") {// We use so many callbacks in order to get the result of the asynchronous operation in the callback of the result processing.return resolve(onFulfilled(value));
            } catch (error) {
              returnreject(error); }}else {
            return resolve(value);
          }
        },
        (error) => {
          if (typeof onRejected === "function") {
            try {
              return resolve(onRejected(error));
            } catch (error) {
              returnreject(error); }}else {
            returnreject(error); }}); }); }; } exports.myPromise = MyPromise;Copy the code

conclusion

  • Promise internally defines the state machine to realize the transition of pending, depressing and Rejected states. The pending state refers to processing internal asynchronous operations. The fulfilled and Rejected indicate the result of successful processing and the failure cause after the asynchronous operation is completed
  • Promises need to have standards that conform to the PromiseA+ specification.then()Method to process the result of the asynchronous output from a Promise.

Refer to the article

  • Medium.com/javascript-…
  • promisesaplus.com/
  • www.promisejs.org/implementin…