A, motivation

Promises are now essential for handling asynchronous operations. Even if you use async await, you end up with something wrapped in a Promise, such as:

Therefore, whether in actual study, work or interview, can not avoid the relevant use and principle of Promise, especially in the interview will even let you realize a Promise. This article mainly introduces some use of Promise and features, and use their own code to achieve its corresponding functions, of course, the focus should be on the realization of the process of understanding, rather than memorizing!!

Two, Promise before knowledge

In order to know something well, you need to know how it works, but you need to know how to use it. Therefore, it is suggested that we should first learn the basic use of the knowledge we do not know, and then contact the underlying principles from the use level.

Basic introduction

  • A Promise instance has three states: Pending, depressing, and Rejected. This state can only be fulfilled or fulfilled. That is, as long as the current state is not pending, the current state cannot be changed. This is a pity or a pity.

  • 1.instantiationPromise, can receive a function as an argument, this function can receiveresolverejectTwo instance methods that change the state of the current instance and pass the parameters they receive to the nextthenIn the corresponding parameter.
    • PS: The callback in the Promise is executed synchronously, but the THEN operation is executed asynchronously
  • 2. The then method can take two functions as arguments. The first function is the same as the resolve callback after the previous PROMISE, and the second function is the same as the reject callback after the previous promise.
  // 1. Instantiate Promise
// Accept a function as an argument that accepts both the resolve and reject instance methods
// Is used to change the state of the current instance and pass the arguments they receive to the next then corresponding argument
  new Promise((resolve, reject) = > {
    // Both resolve and reject can be executed, but neither is meaningful because once a promise state changes, it cannot be changed again
    resolve('ok');
    // reject('err');
  }).then((value) = > {
    console.log("resolve callback = ", value); // If resolve is executed, value = ok
  }, (reason) = > {
    console.log("reject callback = ", reason); Reject: value = err
  });

  // 2. The then method can take two functions as arguments:
  // The first function is equivalent to executing the corresponding callback to resolve in the previous promise
  The second function is equivalent to the callback that executes reject in the previous Promise
Copy the code
  • 3. The default value returned in the two callback functions of THEN can be received by the first callback function in the next THEN, which actually indicates that a new promise will be obtained after the THEN operation, and the state of this new promise will be fulfilled.
By default, the value returned by the first callback in the next THEN is received by the first callback in the next THEN
 // This will be a big pity. // This will be a big pity
 let p1 = new Promise((resolve, reject) = > {
    resolve('ok');
    // reject('err');
  });
  
 let p2 = p1.then((value) = > {
    console.log("resolve p1.then = ", value); // ok
    return 'then ok';
  }, (reason) = > {
    console.log("reject p1.then = ", reason);// err
    return 'then err';
  })
  
  p2.then((value) = > {
    console.log("resolve p2.then = ", value);// ok || err
  }, (reason) = > {
    console.log("reject p2.then = ", reason);
  });
Copy the code
  • 4. Then allows return of a Promise instance, but does not allow return of the same Promise instance. In other words, the same Promise instance cannot return itself in a THEN operation.
  • 5. As long as the callback corresponding to reject in the previous Promise is set in the then method, the error message can be normally received. Otherwise, the error message will be displayed according to the error reporting mechanism of JavaScript.
   // 4. Then allows return of a Promise instance, but does not allow return of the same Promise instance
   // In other words, it cannot return itself in the then operation of the same Promise instance
   let p1 = new Promise((resolve, reject) = > {
      resolve('ok');
      // reject('err');
    });
    
   let p2 = p1.then((value) = > {
      console.log("resolve p1.then = ", value); // ok
      return p2;
    }, (reason) = > {
      console.log("reject p1.then = ", reason);// err
      return p2;
    })
    
    p2.then((value) = > {
      console.log("resolve p2.then = ", value);// ok || err
    }, (reason) = > {
      console.log("reject p2.then = ", reason);// TypeError: Chaining cycle detected for promise #<Promise>
    });
    // 5. The error message will be received as long as the callback corresponding to the reject in the previous Promise is set in the then method. Otherwise, the error message will be displayed as JavaScript's error mechanism.
   
Copy the code
  • 6. Promise.all() receives multiple promises in the form of an array. When all the promises are fulfilled and the status is fulfilled, their execution results will be returned in the form of an array to the first function callback in the next THEN operation. Otherwise a false Promise result will occur and the second function callback will be returned.
    // 1. P1, p2 are all successful
    let p1 = new Promise((resolve, reject) = >{
      setTimeout(() = >{
        resolve('p1 success');
      },1000);
    });

    let p2 = new Promise((resolve, reject) = >{
      setTimeout(() = >{
        resolve('p2 success');
        // reject('p2 fail');
      },2000);
    });

    Promise.all([p1,p2])
    .then(value= > {
      console.log('all promise success = ', value); // all promise success = (2) ["p1 success", "p2 success"]
    },reason= > {
      console.log('one promise fail = ', reason);
    });
    
    // 2. P1 succeeds. P2 fails
      let p1 = new Promise((resolve, reject) = >{
      setTimeout(() = >{
        resolve('p1 success');
      },1000);
    });

    let p2 = new Promise((resolve, reject) = >{
      setTimeout(() = >{
        // resolve('p2 success');
        reject('p2 fail');
      },2000);
    });

    Promise.all([p1,p2])
    .then(value= > {
      console.log('all promise success = ', value);
    },reason= > {
      console.log('one promise fail = ', reason);// one promise fail = p2 fail
    });
Copy the code
  • 7. Promise.race() receives multiple promises as an array. As long as one Promise completes first, this result is returned to the corresponding callback function in the next THEN, regardless of state.
 let p1 = new Promise((resolve, reject) = >{
      setTimeout(() = >{
        resolve('p1 success');
        // reject('p1 fail');
      },1000);
    });

    let p2 = new Promise((resolve, reject) = >{
      setTimeout(() = >{
        resolve('p2 success');
      },2000);
    });

    Promise.race([p1,p2])
    .then(value= > {
      console.log('success = ', value);// success = p1 success
    },reason= > {
      console.log('fail = ', reason);
    });
Copy the code

Three, implement MyPromise

1. Implement basic functions

  • New operation
  • Resovle & Reject
  • Then method
// Three states
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// Promise receives a function argument that executes immediately
class MyPromise {
  constructor(fn) {
    this.value;
    this.status = PENDING;// Default state

    // Try catch is used to catch possible errors
    try {
      // This must be bound, otherwise this will not execute the current instance when called externally
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error)
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED; }}reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;
    }
  }
}

MyPromise.prototype.then = function (onResolve, onReject) {

  // The previous Promise instance called resolve
  if(this.status === RESOLVED){
    onResolve(this.value);
  }

  The last promise instance called reject
  if(this.status === REJECTED){
    onResolve(this.value); }}Copy the code

MyPromise now handles basic Resovle and Reject, including then.

2. Consider asynchronous state changes

  • Exposure problem: Although we implemented the most basic functionality in [1], what would happen if we changed the state asynchronously?
    • What does the following program output? The answer is nothing, because the state is changed asynchronously by setTimeout, and the code in then is all synchronous. Therefore, the then will be executed before the resolve in setTimeout is executed. At this point, the promise state is still pending, while the panding state is not processed.
   // What is the output of this operation MyPromise?
   let p = new MyPromise((resolve, reject) = > {
      setTimeout(() = >{
        resolve(1);
      },1000);
    }).then(value= > {
      console.log('then resolve = ', value);
    }, reason= > {
      console.log('then reject = ', reason);
    });
Copy the code
  • Problem solved: Cache callback, waiting to execute.
// Three states
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// Promise receives a function argument that executes immediately
class MyPromise {

  constructor(fn) {
    this.value;
    this.status = PENDING;// Default state
    this.onResolveCallBack = [];/ / the cache onResolve
    this.onRejectCallBack = [];/ / the cache onReject

    // Try catch is used to catch possible errors
    try {
      // This must be bound, otherwise this will not execute the current instance when called externally
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error); }}resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;
      // 遍历调用 onResolveCallBack
      this.onResolveCallBack.forEach(r= >r()); }}reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;
      // 遍历调用 onRejectCallBack
      this.onRejectCallBack.forEach(r= > r());
    }
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {

  // The current promise instance calls resolve
  if (this.status === RESOLVED) {
    onResolve(this.value);
  }

  // The current promise instance calls reject
  if (this.status === REJECTED) {
    onReject(this.value);
  }

  // The current promise state is pending. Cache the current onResolve & onReject
  if (this.status === PENDING) {
    this.onResolveCallBack.push(() = > {
      onResolve(this.value);
    });
    this.onRejectCallBack.push(() = > {
      onReject(this.value); }); }}Copy the code

3. Then chain call & THEN penetration & asynchronous operation & error capture

  • PS: THEN penetration refers to passing the current promise value to the next THEN when there is no corresponding callback processing in the THEN operation. Such as:

  • Problem exposure:
    • Multiple not implementedthenA chain call between
    • Then penetration is not achieved
    • Resolve & Reject asynchrony is not implemented
    • Not catching all possible errors of the callback function and passing them to the onReject callback of the next THEN
  • Solve a problem:
    • Then returns a new promise
    • Compatibility processing for parameters received in then
    • When the callback is called, it is handled through the setTimeout package
    • Error catching is implemented through a try catch, and then the reject in the promise is called
// Three states
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// Promise receives a function argument that executes immediately
class MyPromise {

  constructor(fn) {
    this.value;
    this.status = PENDING;// Default state
    this.onResolveCallBack = [];/ / the cache onResolve
    this.onRejectCallBack = [];/ / the cache onReject

    // Try catch is used to catch possible errors
    try {
      // This must be bound, otherwise this will not execute the current instance when called externally
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error); }}resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;

      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
        // 遍历调用 onResolveCallBack
        this.onResolveCallBack.forEach(r= >r()); }); }}reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;

      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
        // 遍历调用 onRejectCallBack
        this.onRejectCallBack.forEach(r= > r());
      });
    }
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {
  // ensure that onResolve & onReject are functions
  Then ((v)=>v); then(v)=>v)
  onResolve = typeof onResolve === 'function' ? onResolve : (v) = > v;
  onReject = typeof onReject === 'function' ? onReject : (v) = > v;

  // This is for chain operation
  return new MyPromise((resovle, reject) = > {

    // The current promise instance calls resolve
    if (this.status === RESOLVED) {
      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
        try {
          let result = onResolve(this.value);
          resovle(result); // The next promise will be fulfilled
        } catch(error) { reject(error); }}); }// The current promise instance calls reject
    if (this.status === REJECTED) {
      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
        try {
          let result = onReject(this.value);
          resovle(result); // The next promise will be fulfilled
        } catch(error) { reject(error); }}); }// The current promise state is pending. Cache the current onResolve & onReject
    if (this.status === PENDING) {
      this.onResolveCallBack.push(() = > {
        try {
          let result = onResolve(this.value);
          resovle(result); // The next promise will be fulfilled
        } catch(error) { reject(error); }});this.onRejectCallBack.push(() = > {
        try {
          let result = onReject(this.value);
          resovle(result); // The next promise will be fulfilled
        } catch(error) { reject(error); }}); }}); }Copy the code

5. Logical judgment in then

  • Then manually returns the PROMISE instance, and the next THEN operation depends on the returned Promise state
  • The current Promise instance itself is not allowed to be returned
  • Extraction repetition logic
// Three states
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// Promise receives a function argument that executes immediately
class MyPromise {

  constructor(fn) {
    this.value;
    this.status = PENDING;// Default state
    this.onResolveCallBack = [];/ / the cache onResolve
    this.onRejectCallBack = [];/ / the cache onReject

    // Try catch is used to catch possible errors
    try {
      // This must be bound, otherwise this will not execute the current instance when called externally
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject.bind(this, error); }}resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;

      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
        // 遍历调用 onResolveCallBack
        this.onResolveCallBack.forEach(r= >r()); }); }}reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;

      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
        // 遍历调用 onRejectCallBack
        this.onRejectCallBack.forEach(r= > r());
      });
    }
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {
  // ensure that onResolve & onReject are functions
  Then ((v)=>v); then(v)=>v)
  onResolve = typeof onResolve === 'function' ? onResolve : (v) = > v;
  onReject = typeof onReject === 'function' ? onReject : (v) = > v;

  // This is for chain operation
  let promise = new MyPromise((resolve, reject) = > {

    // The current promise instance calls resolve
    if (this.status === RESOLVED) {
      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
          let result = onResolve(this.value);
          transferPromiseResult(promise, result, resolve, reject);
      });
    }

    // The current promise instance calls reject
    if (this.status === REJECTED) {
      // setTimeout To ensure asynchronous sequential execution
      setTimeout(() = > {
          let result = onReject(this.value);
          transferPromiseResult(promise, result, resolve, reject);
      });
    }

    // The current promise state is pending. Cache the current onResolve & onReject
    if (this.status === PENDING) {
      this.onResolveCallBack.push(() = > {
          let result = onResolve(this.value);
          transferPromiseResult(promise, result, resolve, reject);
      });
      this.onRejectCallBack.push(() = > {
          let result = onReject(this.value); transferPromiseResult(promise, result, resolve, reject); }); }});return promise;
}

// Pass the last promise value to the next THEN
function transferPromiseResult(promise, result, resolve, reject) {
  // To process the current PROMISE instance, the current THEN is returned
  if (promise === result) {
    throw new TypeError('Chaining cycle detected for promise #<MyPromise>');
  }

  try {
    // If the previous then returns an instance of MyPromise && not the same Promise instance
    // Just pass the processed return value from MyPromise to then
    if (result instanceof MyPromise) {
      result.then(resolve, reject);
    } else {
      // Pass the normal result to the next thenresolve(result); }}catch(error) { reject(error); }}Copy the code

6. Implement myPromise.all () & myPromise.race ()

  • We have implemented most of the functions above, but all and Race only need to be implemented based on existing functions.
  • This is a big pity. The result set will be returned if all the promises are fulfilled and the status is fulfilled. Otherwise, the result will be failed.
  • Race –> Receive an array of promises, and the result will be the result of the promise fulfilled first, no matter the state is a pity or Rejected.
// Three states
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// Promise receives a function argument that executes immediately
class MyPromise {
  constructor(fn) {
    this.value;
    this.status = PENDING;
    this.onResolveCallBack = [];
    this.onRejectCallBack = [];

    // execute passing fn
    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      // Use reject when an error occurs
      this.reject(error); }}// The instance method resolve
  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = RESOLVED;

      // setTimeout ensures asynchronous execution
      setTimeout(() = > {
        Execute the callback stored in THEN
        try {
          this.onResolveCallBack.forEach((fn) = > fn && fn(this.value));
        } catch (error) {
          this.onRejectCallBack.forEach((fn) = >fn && fn(error)); }}); }}// Instance method reject
  reject(reason) {
    if (this.status === PENDING) {
      this.value = reason;
      this.status = REJECTED;

      // setTimeout ensures asynchronous execution
      setTimeout(() = > {
        Execute the callback stored in THEN
        this.onRejectCallBack.forEach((fn) = > fn && fn(this.value)); }); }}// Static resolve method
  static resolve(value) {
    return new MyPromise((resolve, reject) = > {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
      } else{ resolve(value); }}); }// Static reject
  static reject(value) {
    console.log(value);
    return new MyPromise((resolve, reject) = > {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
      } else{ reject(value); }}); }// Static method all
  static all(promiseArr) {
    // Record each successful promise result
    const resolveResultArr = [];
    return new MyPromise((resolve, reject) = > {
      promiseArr.forEach(promise= > {
        promise.then(value= > {
          resolveResultArr.push(value);

          // When the length is the same, all promises are completed and the result is successful
          if(promiseArr.length === resolveResultArr.length) { resolve(resolveResultArr); }},reason= > {
          // If a promise fails, it fails
          reject(reason);
        });
      });
    });
  }

  // Static method race
  static race(promiseArr) {
    return new MyPromise((resolve, reject) = > {
      promiseArr.forEach(promise= > {
          promise.then(value= > {
            resolve(value);
          }, reason= > {
            reject(reason);
          });
      });
    });
  }

}

MyPromise.prototype.then = function (onResolve, onReject) {

  // ensure that onResolve & onReject are functions
  Then ((v)=>v); then(v)=>v)
  onResolve = typeof onResolve === 'function' ? onResolve : (v) = > v;
  onReject = typeof onReject === 'function' ? onReject : (v) = > v;

  let promise = new MyPromise((resolve, reject) = > {

    if (this.status === RESOLVED) {
      setTimeout(() = > {
        // Save result with the value returned from the previous THEN
        let result = onResolve(this.value);
        // Pass the value of the next then
        transferPromiseResult(promise, result, resolve, reject);
      });
    }

    if (this.status === REJECTED) {
      setTimeout(() = > {
        // Save result as the value returned from the previous THEN
        let result = onReject(this.value);
        // Pass the value of the next then
        transferPromiseResult(promise, result, resolve, reject);
      });
    }

    // The callback is stored in an array to be called after a state change
    if (this.status === PENDING) {
      this.onResolveCallBack.push(() = > {
        // Save result with the value returned from the previous THEN
        let result = onResolve(this.value);
        // Pass the value of the next then
        transferPromiseResult(promise, result, resolve, reject);
      });

      this.onRejectCallBack.push(() = > {
        // Save result as the value returned from the previous THEN
        let result = onReject(this.value);
        // Pass the value of the next thentransferPromiseResult(promise, result, resolve, reject); }); }});return promise;
}

// Pass the last promise value to the next THEN
function transferPromiseResult(promise, result, resolve, reject) {
  // To process the current PROMISE instance, the current THEN is returned
  if (promise === result) {
    throw new TypeError('Chaining cycle detected for promise #<MyPromise>');
  }

  try {
    // If the previous then returns an instance of MyPromise && not the same Promise instance
    // Just pass the processed return value from MyPromise to then
    if (result instanceof MyPromise) {
      result.then(resolve, reject);
    } else {
      // Pass the normal result to the next thenresolve(result); }}catch(error) { reject(error); }}Copy the code

The end