Promise internal structure analysis and implementation

  • The attribute of Promise
    • PromiseState = ‘pending’
    • PromiseResult = undefined
  • The Promise argument executor(resolve,reject)
    • Executor () is an externally defined, internally synchronized function
    • Resolve () and reject() are internal definitions that are passed to external functions when executor is executed
  • Eg: A Promise will not change in condition after it moves from the eg stage to the first stage
  • By what does a Promise start from pending -> Fullfilled?
    • Active call resolve()
  • What does Promise pass through pending -> Rejected?
    • Active call reject()
    • Exception occurred in synchronized code (cc is not defined in the following example)
      • console.log(cc)
      • throw ‘throw-error’
  • The code implementation is as follows
function MyPromise(executor) {
  this.PromiseState = 'pending'
  this.PromiseResult = undefined
  const self = this
  
  function resolve(value) {
    if(self.PromiseState ! = = ='pending') return
    self.PromiseState = 'fullfilled'
    self.PromiseResult = value
    
  }
  
  function reject(reason) {
    if(self.PromiseState ! = ='pending')
    self.PromiseState = 'rejected'
    self.PromiseResult = reason
  
  }
  
  try {
    executor(resolve,reject) // The executor function executes synchronously
  }catch(reason) {
    reject(reason) // The synchronization code caught an exception}}Copy the code
  <script src="./myPromise.js"></script>
  <script>

    const p = new MyPromise((resolve,reject) = > {
    // 1
      // resolve('resolve')
      // reject('reject')
      // throw 'throw-error'
      // console.log(cc)

    // 2
     // setTimeout(() => {
        // resolve('resolve')
        // reject('reject')
      
      // Async try catch fails!
        // throw 'throw-error'
        // console.log(cc)

     // })
    })

    console.log(p)

   </script>
Copy the code

Then internal structure analysis and implementation

  • OnResolved and onRejected are callback functions for the Promise in the fullfilled and rejected states, respectively
  • The callback function of the THEN method is executed asynchronously
  • The then method returns a Promise, whose state is determined by the callback function
    • If the return value is not Promise, the then method returns the Promise state as fullfilled and PromiseResult as the return value
    • After performing the callback, the return value is a Promise. Then the state of the Promise returned by the then method is determined by the Promise. The PromiseResult is the same as the PromiseResult of the Promise
    • When an exception occurs during the callback, the then method returns the Promise state of Rejected and PromiseResult as the cause of the failure

Eg: Promise sync code makes eg -> settled

  • Eg: In creating a Promise, in the executor function the code is synchronized to make the Promise from eg -> Settled
  const p = new MyPromise((resolve,reject) = > {
    // Internally synchronize code to execute tests
      resolve('resolve')
      // reject('reject')
      // throw 'throw-error'
      // console.log(cc)
  })
Copy the code
  • Eg: In this case, when a Promise creates an instance, after executing the executor function, it is loaded from eg -> Settled
  MyPromise.prototype.then = function(onResolved,onRejected) {
    const self = this
    return new MyPromise((resolve,reject) = > {
    
        function callback(type) {
            // type is onResolved | onRejected
            try {
              const return_value = type(self.PromiseResult)

              if(return_value instanceof MyPromise) {
                if(return_value.PromiseState === 'fullfilled') {
                  resolve(return_value.PromiseResult)
                }else if(return_value.PromiseState === 'rejected') {
                  reject(return_value.PromiseResult)
                }

              }else {
                resolve(return_value)
              }

            }catch(reason) {
              reject(reason)
            }
        }
      
        if(self.PromiseState === 'fullfilled') {
          setTimeout(() = > {
            callback(onResolved)
          })
        }else if(self.PromiseState === 'rejected') {
          setTimeout(() = > {
            callback(onRejected)
          })
        }else if(self.PromiseState === 'pending') {
          console.log(Eg: 'Promise sync code makes eg -> Texas')}}}Copy the code
  • The test case
  <script>
    const p = new MyPromise((resolve,reject) = > {
    // 1
      // resolve('resolve')
      reject('reject')
      // throw 'throw-error'
      // console.log(cc)

    // 2
      // setTimeout(() => {
        // resolve('resolve')
        // reject('reject')
      
      // Async try catch fails!
        // throw 'throw-error'
        // console.log(cc)

      // })
    })

    console.log(p)
    const p1 = p.then(value= > {
      console.log(value)
      // return 'return-value is not Promsie'
      return new MyPromise((resolve,reject) = > {
        // resolve('return_value is Promise, resolve')
        Reject ('return_value is Promise, reject')
        throw 'return_value is Promise, throw'})},reason= > {
      console.log(reason)
      // return 'return-value is not Promsie'
      return new MyPromise((resolve,reject) = > {
        resolve('return_value is Promise, resolve')
        Reject ('return_value is Promise, reject')
        // throw' return_value is Promise, throw'})})console.log(p1)
  </script>
Copy the code

Eg: The Promise asynchronous code makes eg -> Settled

  • Eg: In creating a Promise, asynchronous code is used inside the executor function to make the Promise from eg -> Settled
  const p = new MyPromise((resolve,reject) = > {
    setTimeout(() = > {
        // resolve('resolve')
        // reject('reject')
      
      // Async try catch fails!
        // throw 'throw-error'
        // console.log(cc)})})Copy the code
  • Eg: In this case, when a Promise instance is created in the eg phase, after the executor function is executed, the sync code continues to be executed in the eg phase because JS is a single thread. When executing the THEN method, the code in ‘pending’ (if(self.promisestate === ‘pending’ of the previous code) will be executed, and the Promise will wait until the asynchronous code is executed before becoming settled. So you need to save the onResolved and onRejected functions of the first THEN method of all the Promise object chain calls until it is in the Settled phase
  • The implementation code is as follows
    • Add an attribute to Promise to hold the callback for the above case
    • In resolve | rejecte function executed in sequence in the callback function
    • Because the callbacks of the THEN method are asynchronous, they are executed asynchronously
  • Promise method improvement
  function MyPromise(executor) {...this.then_callbacks = []
    const self = this
    
    function resolve(value) {...setTimeout(() = > {
        self.then_callbacks.forEach(ele= > {
          ele.onResolved()
        })
      })
    }
    function reject(reason) {...setTimeout(() = > {
        self.then_callbacks.forEach(ele= > {
          ele.onRejected()
        })
      })
    }
    
    ...
  }
  
Copy the code
  • Then method perfection
  MyPromise.prototype.then = function(onResolved,onRejected) {
    const self = this
    return new Promise((resolve,reject) = > {
      function callback(type) {... }if(self.PromiseState === 'fullfilled') {... }else if(self.PromiseState === 'rejected') {... }else if(self.PromiseState === 'pending') {
        self.then_callbacks.push({
          onResolved: function() {
            callback(onResolved)
          },
          onRejected: function() {
            callback(onRejected)
          }
        })
      }
      
    })
  }
Copy the code
  • The test case
  <script>
       const p = new MyPromise((resolve,reject) = > {
    // Internal asynchronous code execution matches then
      setTimeout(() = > {
        resolve('resolve')
        // reject('reject')
      
      // Async try catch fails!
        // throw 'throw-error'
        // console.log(cc)})})console.log(p)

    const p1 = p.then(value= > {
      console.log(value)
      // return 'return-value is not Promsie'
      return new MyPromise((resolve,reject) = > {
        // resolve('return_value is Promise, resolve')
        Reject ('return_value is Promise, reject')
        throw 'return_value is Promise, throw'})},reason= > {
      console.log(reason)
      // return 'return-value is not Promsie'
      return new MyPromise((resolve,reject) = > {
        resolve('return_value is Promise, resolve')
        Reject ('return_value is Promise, reject')
        // throw' return_value is Promise, throw'})})console.log(p1)
  </script>
Copy the code

Internal structure analysis and implementation of CATCH

  • Parameter only accepts the onRejected callback function
  • It is a special case of the THEN method, which does not pass onResolved, but it is important to note the native Promise, its THEN and catch functions are passed by exceptions, and its THEN methods are passed by values
  • Abnormal penetration
    • Then method of the second parameter is not preach | not function, abnormal will penetrate down, until to the callback function can accept
  const c = 123
  const t = new Promise((resolve,reject) = > {
    reject('Abnormal penetration')
  })
  t.then(val= > {
    console.log(val)
  }).then(val= > {
    console.log(val)
  },c).catch(reason= > {
    console.log('the catch:,reason)
  })
Copy the code
  • Value passed
    • Then method first parameter travels | don’t preach, not function value can pass it on
  const m = new Promise((resolve,reject) = > {
    resolve('Value passing')
  })
  m.then().then().then(val= > {
    console.log('1-then',val)
  })
  const a = 1
  const b = 'hh'
  m.then(a).then(b).then(val= > {
    console.log('2-then',val)
  })
Copy the code
  • Therefore, we need to improve the then method. When calling it, we need to check whether onResolved and onRejected are functions. If not, we need to fill in the default functions for them
    • The default onResolved(value) implements the value transfer function
      • return value
    • The default onRejected(Reason) implements exception penetration
      • throw reason
  • The then method is perfected as follows
    • The test code can use the one above to test the native Promise
  MyPromise.prototype.then = function(onResolved,onRejected) {
    if(typeofonResolved ! = ='function') {
      onResolved = (value) = > value
    }
    if(typeofonRejected ! = ='function') {
      onRejected = (reason) = > {
        throw reason
      }
    }
    
    ...
    
  }
Copy the code

Finally internal structure analysis and implementation

  • The argument accepts a callback function, onFinally
  • How it’s executedlikeAs a special case of the THEN method, a Promise executes the specified callback function in its state, whether completed or Rejected, in the Settled phase
    • But it’s not the same thingthen(onFinally,onFinally)
      • The onFinally function takes no arguments
      • Return a Promise object that sets the finally callback (that is, the Promise that calls the finally callback)
  • The implementation code
  MyPromise.prototype.finally = function(onFinally) {
    setTimeout(() = > {
      onFinally()
    }) 
    return this
  }
Copy the code
  • The test code

    const k = new MyPromise((resolve,reject) = > {
      resolve('resolve')
      reject('reject')})const k1 = k.then().then(val= > {
      throw 'throw-resolve'
    }).catch(reason= > {
      return new MyPromise((resolve,reject) = > {
        reject('finally-rejected')
        // resolve('finally-fullfilled')
      })
    }).finally((data) = > {
      console.log(data,'onFinally')
      // throw 'new-throw'
    })
    console.log(k1)
Copy the code

Resolve internal structure analysis and implementation

  • Return a Promise
    • If the argument is Promise, it is returned
    • If a parameter is thenable (contain then method of Object | Function)
      • Then calls the successful callback, returns the fullfilled Promise, PromiseResult as the successful callback parameter
      • Then calls the failed callback and returns the PromiseResult of the Rejected callback
    • If the parameter is any other type, return a fullfilled Promise, PromiseResult
  • Code implementation
  MyPromise.resolve = function(value) {

  if(value instanceof MyPromise) {
    // Promise
    return value

  }else if(value['then'] instanceof Function) {
    // thenable
    value.then(v= > {
      return new MyPromise((resolve,reject) = > {
        resolve(v)
      })
    },r= > {
      return new MyPromise((resolve,reject) = > {
        reject(r)
      })
    })

  }else {
    / / other values
    return new MyPromise((resolve,reject) = > {
      resolve(value)
    })
  }

}
Copy the code
  • Mdn-resolve verifies test cases

Reject internal structure analysis and implementation

  • Returns a Promise in the Rejected state. PromiseResult is a reject parameter
MyPromise.reject = function(reason) {
  return new MyPromise((resolve,reject) = > {
    reject(reason)
  })
}
Copy the code
  • Mdn-reject Verify test cases

All internal structure analysis and implementation

  • Note that Iterable’s promises are in the Settled stage and will execute their callback functions asynchronously
  • Parameter arg is an iterable (Array | String) and returns a Promise
    • If arGs are all promises, return fullfilled Promise, PromiseResult as []
    • If arG does not contain any promises, return the fullfilled Promise, PromiseResult as ARG
    • If there’s a Promise in the ARG
      • Return the fullfilled Promise, PromiseResult as array arr
        • If arg[I] is not a Promise, then arr[I] = arg[I]
        • If arg[I] is a Promise, arr[I] = arg[I].promiseresult
      • If arG [I] is a Promise, and the Texas stage is in the Rejected state, return the rejected Promise, PromiseResult is arg[I].promiseresult
  • The code implementation is as follows
  MyPromise.all = function(promises) {
  return new MyPromise((resolve,reject) = > {
    
    let len = promises.length
    let arr = []
    let count = 0 // Record the Promise in the fullfilled state
    let flag = 1 // Promise array is not Promise, 1 is not Promise, 0 is not Promise
    for(let value of promises) {
      if(value instanceof MyPromise) {
        flag = 0}}if(len === 0) {
      // Empty iterable
      resolve([])
    }else if(flag === 1) {
      // Promises do not contain any promises
      resolve(promises) // Google Chrome 58 returns a completed state Promise
    }else {
      // Promises are promises
      for(let i = 0; i < len; i ++) {
        
        if(promises[i] instanceof MyPromise) {
          // console.log(i,len)
          promises[i].then(v= > {
            count ++
            arr[i] = v
            if(count === len) {
              resolve(arr)
            }
    
          },r= > {
            reject(r)
          })
    
        }else {
          
          count ++
          arr[i] = promises[i]

        }

      }
    }
  })

} 
Copy the code
  • Mdn-all Test case

Analysis and implementation of race internal structure

  • The parameter is iterable
  • Return to the Promise
  • Note that Iterable’s promises are in the Settled stage and will execute their callback functions asynchronously
  • Whichever Promise in the ARG reaches the first settled stage returns a Promise with the same state and result
MyPromise.race = function(promises) {

  return new MyPromise((resolve,reject) = > {
    const len = promises.length 
    for(let i = 0; i < len; i ++) {
      promises[i].then(v= > {
        resolve(v)
      },r= > {
        reject(r)
      })

    }

  })

}
Copy the code
  • Mdn-race test case

AllSettled internal structure analysis and implementation

  • The parameter is iterable
  • Note that Iterable’s promises are in the Settled stage and will execute their callback functions asynchronously
    • A pending Promise that will be asynchronously fulfilled once every promise in the specified collection of promises has completed, either by successfully being fulfilled or by being rejected.
  • Return to the Promise
    • When each member is settled, PromiseResult is an array of objects, each corresponding to a Promise
      • { value: ... , status: ... }
      • { reason: ... , status: ... }
    • Members have not Promise | has Promise members in a pending state, PromiseResult is undefined
  • The code implementation is as follows
MyPromise.allSettled = function(promises) {
  return new Promise((resolve,reject) = > {
    let len = promises.length
    let count = 0 // Record the number of promises in the settled stage
    let arr = []
    for(let i = 0; i < len; i ++) {
      const promise = promises[i]

      if(promise instanceof MyPromise) {
        
        const status = promise.PromiseState
        promise.then(value= > {
          count ++
          arr[i] = {
            status,
            value,
          }
          if(count === len)
            resolve(arr)
    
          
        },reason= > {
          count ++
          arr[i] = {
            status,
            reason,
          }
          if(count === len)
            resolve(arr)
    

        })

      }

    }
    
  })

}

Copy the code
  • MDN-allSettled test case

Any internal structure analysis and implementation

  • Parameter iterable
  • Return a Promise
    • If iterable is empty, return the Promise of Rejected
    • If iterable contains non-promise data, return asynchronous fullfilled, PromiseResult as the first non-promise data in the first non-iterable
    • If iterable is all Promise
      • If one of the promises in iterable becomes fullfilled, the returned Promise becomes fullfilled asynchronously
      • If iterable’s promises are changed to Rejected, then the returned Promise is asynchronously changed to Rejected
      • In other cases, pending
  MyPromise.any = function(promises) {
  return new MyPromise((resolve,reject) = > {

    let len = promises.length
    let reject_count = 0 // Count the number of promises that fail
    let index = -1 // Record the first non-PROMsie location
    let errors = []
    for(let i = 0; i < len; i ++) {
      if(! (promises[i]instanceof MyPromise)) {
        index = i
        break}}if(len === 0) {
      reject({
        errors,
        message: "All promises were rejected".stack: "AggregateError: All promises were rejected"
      }) // Empty iterator
    }else if(index ! = -1) {
      setTimeout(resolve,0,promises[index]) // The iterator has a non-promise and returns the first non-promise
    }else {
      
      for(let j = 0; j < len; j ++) {
        if(promises[j] instanceof MyPromise) {
          promises[j].then(v= > {
            resolve(v)
          },r= > {
            reject_count ++
            errors[j] = r
            if(reject_count === len) {
              reject({
                errors,
                message: "All promises were rejected".stack: "AggregateError: All promises were rejected"})}})}}}})}Copy the code
  • MDN-any case verification

Deficiency in

  • (1) The then and other methods use setTimeout to simulate the asynchronous call callback function, but the timer belongs to the macro queue, while the then and other methods asynchronously call the callback function, which is added to the micro queue, so it will be slightly different from the original
  • MDN if-then case
  Promise.resolve("foo")
  // 1. Accept "foo" and concatenate it with "bar", and return the result as the next resolve.
  .then(function(string) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        string += 'bar';
        resolve(string);
      }, 1);
    });
  })
  // 2. Receive "foobar" and put it into an asynchronous function to process the string
  // Prints it to the console, but does not return the processed string to the next.
  .then(function(string) {
    setTimeout(function() {
      string += 'baz';
      console.log(string);
    }, 1)
    return string;
  })
  // 3. Print a help message for how the code in this section will work,
  // The string is actually processed by the block of asynchronous code that precedes the last callback function.
  .then(function(string) {
    console.log("Last Then: oops... didn't bother to instantiate and return " +
                "a promise in the prior then so the sequence may be a bit " +
                "surprising");

    // Notice that 'baz' does not exist in 'string'.
    // Because this happens in an asynchronous function that we emulate with setTimeout.
    console.log(string);
  });

// logs, in order:
// Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising
// foobar
// foobarbaz
Copy the code
  • In the Promise I implemented, nothing printed on the console
    • Eg: In this situation, as the return value of the callback function inside the eg then is a Promise object, Deals only with a fullfilled and rejected state, did not deal with the operation of the pending state (that is, the asynchronous operation to complete unsettled – > hills)
  • Perfect then
  MyPromise.prototype.then = function(onResolved,onRejected) {...return MyPromise((resolve,reject) = > {
      try{...if(return_value instanceof MyPromise) {
          ...
          else if(return_value.PromiseState === 'pending') {
            return_value.then(v= > {
              resolve(v)
            },r= > {
              reject(r)
            })
          }
        }
      }catch{... }})}Copy the code

  • Once perfected, you can see that the output is correct, but not in the same order as the original Promise
  • At first I was wondering what was causing the order to be different, and then I was thinking in terms of asynchronous execution, event loops, maybe it was because the microqueue had a higher priority than the macro queue,Promises are written using setTimeout (macro queue) to simulate asynchronously executing the then callbackAfter analyzing the operation process, it is found that this is indeed the reason
    • The then callback is added to the microqueue after the bound Promise is fullfilled
    • Below for each timer more than with the console. The log (‘ 1 ‘|’ 2 ‘|’ 3 ‘)
    const p = Promise.resolve("foo")
    // 1. Accept "foo" and concatenate it with "bar", and return the result as the next resolve.
    .then(function(string) {
      console.log('then-1')
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          console.log('setTimeout-1')
          string += 'bar';
          resolve(string);
        }, 1);
      });
    })
    // // 2. Accept "foobar" and put it into an asynchronous function to process the string
    // // and prints it to the console, but does not return the processed string to the next.
    .then(function(string) {
      console.log('then-2')
      setTimeout(function() {
        console.log('setTimeout-2')
        string += 'baz';
        console.log(string);
      }, 1)
      return string;
    })
    // 3. Print a help message for how the code in this section will work,
    // The string is actually processed by the block of asynchronous code that precedes the last callback function.
    .then(function(string) {
      console.log('then-3')
      console.log('setTimeout-3')
      console.log("Last Then: oops... didn't bother to instantiate and return " +
                  "a promise in the prior then so the sequence may be a bit " +
                  "surprising");

      // Notice that 'baz' does not exist in 'string'.
      // Because this happens in an asynchronous function that we emulate with setTimeout.
      console.log(string);
    });
Copy the code
  • His writing

    • Resove (‘foo’) returns a fullfilled Promise, and adds the callback to the macro queue
      • Macro queue: then1
    • Then1 from the macro queue, execute it, asynchronously return a fullfilled Promise via setTimeout1, start pending, and add then2 to the macro queue when it goes fullfilled
      • Macro queue: then1 -> setTimeout1 -> then2
    • Unqueue THEN2, put setTimeout2 into the macro queue, return a fullfilled Promise, and queue then3
      • Macro queue: then2 -> setTimeout2 -> then3
    • 4. Remove then3 from the macro queue and execute it
    • Then1 -> setTimeout1 -> then2 -> setTimeout2 -> then3
  • The native

    • Resove (‘foo’) returns a fullfilled Promise, putting the then callback into the microqueue
      • Microqueue: then1
      • Macro queue: empty
    • Then1 is removed from the microqueue, and then executed. SetTimeout1 asynchronously returns a fullfilled Promise that is initially pending. SeTimeout is executed, and then goes fullfilled
      • Microqueue: empty -> then2
      • Macro queue: setTimeout1 -> empty
    • Then2 is removed from the microqueue, setTimeout2 is placed on the macro queue, and a fullfilled Promise is returned. The next then callback is placed on the microqueue, and the stack is empty because the microqueue has a high priority
      • Microqueue: then2 -> then3
      • Macro queue: empty -> setTimeout2
    • 4. Fetching then3, execute stack empty, fetching macro queue setTimeout2
      • Therefore, the above output timer 2 will output last
    • Then1 -> setTimeout1 -> THEN2 -> THEN3 -> setTimeout2
  • (2) In native Promises, a Promise in the Rejected state must bind a failed callback function to catch an exception, or it will throw an exception

    • throw new Error(‘Uncaught (in promise) ‘+ this.PromiseResult)

  • (3) Note that the script tag is queued into the macro, so the encapsulated Promise will differ from the native Promise if written in a different script tag

END

  • From the beginning, I followed the encapsulation and now I can independently encapsulate a relatively perfect Promise. I encountered many problems on the way, but I finally solved it after visiting various forums, Google and Github. Then I wanted to share the following through blog articles
    • If there is a mistake above, also hope the reader can point out
  • I recommend a silicon Valley axios source code reading (to learn about an Axios running process)
  • Github source code
  • Mind maps drawn by onProcess

  • Typesetting tool: MDnice

reference

  • When did the Promise callback for then enter the microtask queue?
  • MDN – Promise
  • B station is still silicon Valley Promise package
  • Native ES5 encapsulated Promise objects
  • Let’s get to the JavaScript runtime mechanism