I’ve used promise for A long time, but I still don’t have much of the promise /A+ specification and in-depth understanding, so take some time to sort it out for the sake of old age amnesia. I’ve seen a lot of flawed promise use in code written by so-called senior developers.

1: Ask some questions

1: Can all specified callbacks be executed?

    var p = new Promise(function(resolve,reject){
        resolve(100)
    })
    
     p.then(function(res){
         console.log('11', res);
     })

     p.then(function(res){
         console.log('22', res);
     })
Copy the code

You can execute them all and print 100.

2: Change the promise state and specify which callback executes first and who executes later?

    // Scenario 1: Change the state of the promise first or implement then first
    var p = new Promise(function(resolve,reject){
        resolve(100)
    })
    
     p.then(function(res){
         console.log(res);
     })
     
     / / scenario 2
    var p = new Promise(function(resolve,reject){
        setTimeout(() = >{
           resolve(100)},2000)
    })
    
     p.then(function(res){
         console.log(res);
     })
    
Copy the code
  • Scenario 1: Synchronous call changes state firstresolveThen specify the callback functionthen.
  • Scenario 2: IfreolveIf yes, it is asynchronousthenSpecify the callback function first, then change the state.

3: What determines the result returned by the then method?

    var p = new Promise(function(resolve,reject){
        resolve(100)})// What determines the status of res?
    var res = p.then(function(res){
        // 1: an exception is thrown. The RES state is a failed state. Fullfiled is error
        // throw 'error';

        // 2: if the result is not a Promise class, the state changes to success and the result is 100 and the state is fullfulLED
        // return 100
        
        // 3: If the result is a promise, the promise determines the res status: the result is 200 and the state is fullfulLED
        return new Promose((resolve,reject) = >{
            resolve(2000)})})Copy the code

Conclusion:thenReturns thepromise It is important that the state is determined by the return value of the currently specified callback function.

4: Chain back print results?

    var p = new Promise(function(resolve,reject){
        setTimeout(() = >{
            resolve(100);
        },2000)}); p.then((result) = >{
        return new Promise((resolve,reject) = >{
            resolve(200)
        })
    })
    .then((res) = >{
        console.log(res) / / 200
    })
    .then((res) = >{
        console.log(res); // undefined
    })
Copy the code

The last currently specified callback function is undefined because the callback specified for it has no return value of its own.

5: How do you organize a chain call to continue?

var p = new Promise(function(resolve,reject){
   setTimeout(() = >{
        resolve(100)},2000)
})

p.then(function(res){
   console.log(Print '1', res);
   return new Promise(() = >{}) 
})
.then(function(res){
   console.log(Print '2', res);
})
Copy the code

Specify a PEDding state promise to prevent further chained calls. If this is not added, the result will be 100 and undefined.

If the above questions are clear, the following is a waste of time. 六四屠杀


2: Preparation before writing promises

Features: State irreversible, support chain call

As you can see from the figure above, the Promise instance object properties:

1: [PromiseState] : indicates three states of an asynchronous task

  • pedding
  • resolved / fulfilled
  • rejected / fullfiled

2: [PromiseResult]: saves the result of asynchronous task failure

There are only three ways to change a promise state:resolve.rejectAnd throw exceptions.

3: Start writing

3.1 Resolve and Reject basic classes are established

Executor is a function that executes immediately after a promise is instantiated, so the basic framework is laid out first. Use es6 syntax for simplicity.

class Promise {
  constructor(executor) {
  
    // State of the promise
    this.PromiseState = "pedding";
    
    // The result of the promise
    this.PromiseResult = undefined;
    
    // return the function container for later use
    this.callbacks = []; 

    // Throw an exception to change the state of the Promise
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e); }}// 1: Change status: irreversible, execute pedding-> succeeded/failed
  // 2: Sets the result value
  resolve(value) {
    if (this.PromiseState === "pedding") {
      this.PromiseState = "fullfilled";
      this.PromiseResult = value; }}reject(error) {
    if (this.PromiseState === "pedding") {
      this.PromiseState = "fullfiled";
      this.PromiseResult = error; }}}Copy the code

There are three types of encapsulation for modifying the Promise state, including reject, which reports an exception.

Check it out and you can cover all three scenarios

 var p = new Promise(function(resolve,reject){

      resolve(100);// reject('233')
      
      // throw 'errr'.
 })
Copy the code

3.2 Encapsulation of the THEN method and asynchronous promise

This section addresses three issues:

  • asynchronouspromise
  • thenMethod encapsulation and multiple calls: As mentioned in the theoretical issues section above, asynchrony is executed firstthenRegister back to the function, and then change the state. To take advantage of this feature, we add a queue to control callback functions registered multiple times.
  • Maintain the same execution order as native, add toMicro tasks.

The Reject code, almost like resolve, does not repeat.

  then(onFullfilled, onFullFiled) {
    //1,2 if corresponds to synchronization
    if (this.PromiseState === "fullfilled") {
      onFullfilled(this.PromiseResult);
    } else if (this.PromiseState === "fullfiled") {
      onFullFiled(this.PromiseResult);
    }

    // If the constructor is asynchronous, it will be executed here first
    if (this.PromiseState === "pedding") {
      // Save back to function
      this.callbacks.push({ onFullfilled, onFullFiled }); }}// 1: Changes the status
  // 2: Sets the result value
  // async: return queueMicrotask after state change
   resolve(value) {
      if (this.PromiseState === "pedding") {
        this.PromiseState = "fullfilled";
        this.PromiseResult = value;
      }

      if (this.callbacks.length) {
         queueMicrotask(() = >{
           this.callbacks.forEach((resolvefn) = >{ resolvefn.onFullfilled(value); }); }}})Copy the code

QueueMicrotask registers a microtask and is currently one of the few functions on the browser side that can register a microtask directly. We could use other methods, and some people might say that the API is incompatible, and so on, but we definitely want to rewrite everything exactly the same, but we can’t actually use this wrapper Promise in production.

Validation:

     
     // Async registration is ok
    var p = new Promise(function(resolve,reject){
       setTimeout(() = >{
         resolve(1000)},2000)
    })

    p.then(function(res){
        console.log(res);
    });

    p.then(function(res){
        console.log(res);
    });
    
    
    // Execution is microtask as well as native
    setTimeout(() = >{
        console.log('Timer')})var p = new Promise(function(resolve,reject){
        console.log(22)        
        resolve(1000)
    })

    p.then(function(res){
        console.log(res);
    });
    
    console.log(1)

    p.then(function(res){
        console.log(res);
    });
    
Copy the code

The interview cut-off line

If you’re reading this article for an interview, you can return it here, as long as you make these key points during the interview, handwritten promises are generally fine, and other apis and more details will be implemented later.

The return value for then is a Promise

3.3.1 Add return value in case of resynchronization of then function

Why write this, please refer to the above part of the problem theory. Three scenarios are verified:

 then(onFullfilled, onFullFiled) {
    return new Promise((resolve, reject) = > {
      // 1,2 if corresponds to synchronization
      if (this.PromiseState === "fullfilled") {
        try {
          var result = onFullfilled(this.PromiseResult);
          if (result instanceof Promise) {
            result.then(
              (s) = > {
                resolve(s);
              },
              (e) = >{ reject(e); }); }else{ resolve(result); }}catch(e) { reject(e); }}else if (this.PromiseState === "fullfiled") {
        onFullFiled(this.PromiseResult);
      }

      // If the constructor is asynchronous, it will be executed here first
      if (this.PromiseState === "pedding") {
        // Save back to function
        this.callbacks.push({ onFullfilled, onFullFiled }); }}); }Copy the code

Validation:

var p = new Promise(function(resolve,reject){
    resolve(1000)})var res = p.then(function(data){
    // console.log(data);
    // throw 'error'
    return new Promise((resolve,reject) = >{
         reject('fk')})return 100;
});

console.log(res)
Copy the code

The three return value scenarios are verified.

3.2.2 The then function is asynchronous
then(onFullfilled, onFullFiled) {
    return new Promise((resolve, reject) = > {
      // 1,2 if corresponds to synchronization
      if (this.PromiseState === "fullfilled") {... With the above// If the constructor is asynchronous, it will be executed here first
      if (this.PromiseState === "pedding") {
        // Save back to function
        this.callbacks.push({
          // This callback is automatically executed whenever the resolve state changes, foreach
          onFullfilled: (res) = > {
            try {
              var result = onFullfilled(res);  // res == this.PromiseResult
              if (result instanceof Promise) {
                result.then(
                  (s) = > {
                    resolve(s);
                  },
                  (e) = >{ reject(e); }); }else{ resolve(result); }}catch(e) { reject(e); } }, onFullFiled, }); }}); }Copy the code

The change is that this.callbacks. Push changes from direct pass to check return value, because the then callback needs to be the same as synchronous writing above, check three scenarios, verify three scenarios, verify.

3.3 packaging

The above two parts of the code are simply to do one more thing:

 /*** * The return result is a PROMISE, and it is important to modify the returned promise state based on the result of executing the function. Undefined */
  then(onFullfilled, onFullFiled) {
    return new Mypromise((resolve, reject) = > {
      function callbacks(handleType) {
        try {
          let res = handleType(this.PromiseResult); // Then returns the result of executing the function
          if (res instanceof Promise) {
            // If it is a promise type, there must be a then method
            res.then(
              (suc) = > {
                resolve(suc);
              },
              (e) = >{ reject(e); }); }else {
            // The result object status is successresolve(res); }}catch (e) {
          // Accept exception errors such as throw to modify the statereject(e); }}//1,2 if corresponds to synchronization
      if (this.PromiseState === "fullfilled") {
        // fullfilled resolved
        setTimeout(() = > {
          callbacks.call(this, onFullfilled);
        });
      } else if (this.PromiseState === "fullfiled") {
        // fullfiled rejected
        setTimeout(() = > {
          callbacks.call(this, onFullFiled);
        });
      }
      // The corresponding is asynchronous
      else if (this.PromiseState === "pedding") {
        this.callbacks.push({
          onFullfilled: () = > {
            A successful callback will be executed
            callbacks.call(this, onFullfilled);
          },
          onFullFiled: () = > {
            callbacks.call(this, onFullFiled); }}); }}); }Copy the code
3.4 Catch and value passing and exception penetration capture
 catch(onRejected) {
    return this.then(undefined, onRejected);
 }

 then(onFullfilled, onFullFiled) {
 // When multiple THEN calls are made, the value is passed. The preceding THEN can not be written to the listener, and the following can be captured
    if (typeofonFullfilled ! = ="function") {
      onFullfilled = (result) = > result;
    }

    // Catch then when the second listener is not written, a default function is passed
    if (typeofonFullFiled ! = ="function") {
      onFullFiled = (e) = > {
        throwe; }; }... }Copy the code
3.5 all methods
// All failures must be returned in accordance with the search
  static all(prolist) {
    return new this((resolve, reject) = > {
      let count = 0;
      let res = [];

      // The entire for loop is asynchronous, but asynchronous is guaranteed by the queue, so the return value is still returned by the hunt
      for (let i = 0; i < prolist.length; i++) {
        const element = prolist[i];
        element.then(
          (v) = > {
            // Knowing that each proList state is successful,
            count++;
            res[i] = v;
            if (count == prolist.length) resolve(res);
          },
          (e) = >{ reject(e); }); }}); }Copy the code
3.6 Static Reolve and Reject
 // The static resolve method must remember to return a promise
  static resolve(data) {
    // Static this refers to the class
    return new this((resolve, reject) = > {
      if (data instanceof Promise) {
        data.then(
          (suc) = > {
            resolve(suc);
          },
          (e) = >{ reject(e); }); }else{ resolve(data); }}); }static reject(data) {
    // Static this refers to the class
    return new this((resolve, reject) = > {
      if (data instanceof Promise) {
        data.then(
          (suc) = > {
            resolve(suc);
          },
          (e) = >{ reject(e); }); }else{ reject(data); }}); }Copy the code
3.7 race

  // Return that as soon as possible
  static race(prolist) {
    return new this((resolve, reject) = > {
      for (let i = 0; i < prolist.length; i++) {
        const element = prolist[i];
        element.then(
          (v) = > {
            resolve(v);
          },
          (e) = >{ reject(e); }); }}); }Copy the code

4: conclusion

Promise’s design is well worth studying, not just for interviews, but for writing all of the above code in an interview.