preface

A thesis and a graduation project… I’ve been broken for so long… Pick up your mood and start again! 😀

In Can I use, we can see that the Promise function compatibility is not particularly good, but our own implementation of the Promise can at least be compatible with IE9+ oh, the code in the article is separated by me, is focused on partition block explanation, if you need source code and compressed version, It has been uploaded to my GitHub[utils.js] welcome star✨

Before we write our favorite promises, let’s take a systematic look at asynchronous programming. Asynchronous programming is a very important concept in JavaScript, but it’s also a very special kind of JavaScript that we call “single-threaded asynchronous programming.”

JS single thread asynchronous programming

JS is single threaded

  • In other words, JavaScript can only do one thing at a time,” you can’t execute the code below until the code above is executed.”

    • There are also some operations in JavaScript that are programmed asynchronously
  • But JavaScript is not the multi-threaded development of our traditional programming, where you can do multiple things at once, because it’s single-threaded, so in fact, if the JavaScript engine is doing something, it can’t do anything else.

  • The browser builds an “asynchronous programming effect” based on EventQueue and EventLoop.

“Asynchronous macro Task”

  • The timer
  • DOM events
  • HTTP requests (Ajax, FETCH, JSONP…)
  • .

“Asynchronous microtasks”

  • Promise “resolve/reject/then…”
  • async await
  • requestAnimationFrame
  • .

The order in which entries to the EventQueue are found:

  1. First go to the asynchronous microtask queue to find:
  • Find the asynchronous microtask of the executable condition and move it toESStackTo the main thread (main thread is busy)
  • If the main thread is idle again, search as described above
  • If multiple tasks are found to be executable at a time, the first one to reach the executable stage will be executed first
  • If there are no asynchronous microtasks to execute (it does not mean there are no asynchronous microtasks), go to phase 2 to continue the search
  1. Then look in the asynchronous macro task queue:
  • This is similar to the search in phase 1
  1. EventLoop:
  • Tail Step step step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step

Promise Execution process

In general, we use promises (RES, REj) to manage asynchronous programming

Success:

let p1 = new Promise( (resolve, reject) = > {
	resolve('ok') 
        [[PrimiseState]]: 'fulfuilled' 
        [[promiseResult]]: 'ok'
  
})
Copy the code

Failure: The handling mechanism for errors is similar to try.. catch

let p1 = new Promise( (resolve, reject) = > {

    resject('no') 
    // If an error is encountered
    [[PromiseState]]: 'rejected'
    [[PromiseResult]]: 'Error cause'
})
Copy the code

A change in the state of an instance that controls the execution of one of the two methods stored in the then method

  • p.then(onfulfilledCallback,onrejectedCallback)

  • The status successfully executed is: onledCallback

  • If the status fails, execute onrejectedCallback

  • And pass the value of [[PromiseResult]] to the method

p1.then(result= > {
    console.log('success -- >', result);
}, reason= > {
    console.log('failure -- >', reason);
});
Copy the code
  1. First, store the onfailledCallback and onrejectedCallback passed in in a container: you can store multiple callbacks based on then

  2. Second, verify the state of the current instance

    • If the instance state is pending, no processing is done
    • If there is a pity/Rejected, the corresponding callback function will be notified to perform “But not immediately, but put it in the microtask queue in EventQueue”.

“Promises themselves are not asynchronous and are used to manage asynchrony, but then methods are asynchronous” microtasks.”

Then the chain

Executing the THEN method returns a brand new Promise instance

let p1 = new Promise((resolve, reject) = > {
    resolve('OK');
    // reject('NO');
});
let p2 = p1.then(result= > {
    console.log('P1 success -- >', result);
    return Promise.reject(10);
}, reason= > {
    console.log('P1 failure -- >', reason);
});
Copy the code

Executing the THEN method returns some brand new Promise instance P2

let p1 = new Promise((resolve, reject) = > {
    resolve('OK');
    // reject('NO');
});
let p2 = p1.then(result= > {
    console.log('P1 success -- >', result);
    return Promise.reject(10);
}, reason= > {
    console.log('P1 failure -- >', reason);
});
Copy the code
  • How did p2’s state and value change?

  • It doesn’t matter which of the oncallledCallback/onrejectedCallback methods is executed based on p1.then

  1. As long as the method executes without error:
    1. If a brand new Promise instance is returned in the method, the success or failure of the “brand new Promise instance” determines the success or failure of P2
    2. What if it doesn’t return a promise? the[[PromiseState]]:fulfiled [[PromiseResult]]Return values:
  2. If method execution fails:
    1. p2[[PromiseState]]: rejected [[PromiseResult]]: Error cause

If onfulfilledCallback/onrejectedCallback fails to pass, the state and the results will be postpone/through to the next callback function of the same status should be performed on the “internal is actually added some implementation effect of the default function”

Promise.all

Promise.all([Promise array :{request that each item in the array be as much as possible a Promise instance}]) :

Promise.all also creates a new Promise instance (AA) that requires all Promise instances in the array to succeed before AA succeeds

Promise.race

Whether the promise instance that knows its state first succeeds or fails determines whether the AA succeeds or fails


Once we’re familiar with the entire implementation process of a Promise, we can parse out its basic structure

The basic structure of promises

Promise the skeleton

Based on the above, we can see that the four most important components of a Promise construct are

  1. PromiseResultHolds the execution result of the argument function
  2. PromiseStateStores the current state currently used to influence the result of the execution of the THEN function
  3. onFulfilledCallbacks/onRejectedCallbacksThe method to be executed in the successful and failed statesEvent pool
  4. Inside the argument functionState, resultmodifiedChange the function

Let’s take a look at the Promise constructor for now, leaving out the implementation of THEN. Of course, if you’re not familiar with constructors and object orientation, we suggest you take a look at prototypes and prototype chains: How do YOU implement call, bind, new? About the execution of new in

stateDiagram-v2
new&nbspPromise(executor) --> executor(resolve,reject)
executor(resolve,reject) --> resolve(change)
executor(resolve,reject) --> reject(change)
resolve(change) --> change('fulfilled',&nbspresult)
reject(change) --> change('rejected',&nbspreason)

// A basic Promise should look like this

function Promise (executor) {

    // The current instance
    var self = this.// A function that changes state and result
        change

    // Only promise instances can be constructed based on new
    if(! (selfinstanceof Promise)) throw new TypeError('undefined is not a promise! ')
    // The executor passed in must be a function
    if (typeofexecutor ! = ='function') throw new TypeError('Promise resolver ' + executor + ' is not a function! ')

    // Store status and result
    self.PromiseResult = undefined
    self.PromiseState = 'pending'
    // Success and failure event pools
    self.onFulfilledCallbacks = []
    self.onRejectedCallbacks = []
    
    change = function change (state, result) {
        // If the state has changed, no processing is done
        if(state ! = ='pending') return 
        
        self.PromiseState = state
        self.PromiseResult = result
        
        // Get the event pool based on the current state
        let callbacks = self.PromiseState === 'fulfilled' ? self.onFulfilledCallbacks : self.onRejectedCallbacks
        
        var len = callback.length,
            i = 0
        if ( len > 0 ) {
            // I couldn't create a microtask here, so I wrapped it with a macro task,
            // Similar to asynchronous execution of Promise, but the function inside then executes an asynchronous microtask,
            // This can't be done by self-implementation.
            setTimeout(function(){
                for (i; i < len; i++) {
                   let callback = callbacks[i]
                   // Execute the function in the event pool and pass out the result
                   if(typeof callback === 'function') callback(self.PromiseResult)
                }
            }, 0)}}// The argument function passed in the Promise is executed synchronously,
    // But its execution success or failure will affect the state and result, so use try.. Catch make the package
    try {
        executor(
            function resolve (result) {
                change('fulfilled', result)
            },
            function reject (reason) {
                change('rejected', reason)
            }
        )
    } catch (err) {
        // If an error occurs during function execution, the error status and result are captured directly
        change('rejected', err) 
    }
    
}
Copy the code

Even if a complete promise is written without considering THEN, there is internal control on asynchrony. Next, we will deal with THEN/THEN chain and catch. Since catch is relatively simple, we will directly look at the implementation process of then chain

Then with then chain

stateDiagram-v2 new&nbspPromise(executor) --> executor(resolve,reject): executor(resolve,reject) --> resolve(change) executor(resolve,reject) --> reject(change) resolve(change) --> change('fulfilled',&nbspresult) reject(change) --> change('rejected',&nbspreason) change('fulfilled',&nbspresult) --> Then (onFulfilled,&nbsponRejected) --> new&nbspPromise(executor): The then chain returns a promise instance new&nbspPromise(executor) --> PromiseState: This is a big pity. Judge the status of PromiseState --> default default --> OnledCallbacks: Pending state default --> onRejectedCallbacks: This is a pity --> handle(newPromise,&nbspx), &NBspresolve, &NBspReject) Handle (newPromise,&nbspx(successful function execution result),&nbspresolve,&nbspreject) --> resolve(x): Then (ondepressing, &nbspReject): this is a pity, &nbspReject: X is an instance of promise

Since this section is messy, I’ll separate out all the code and look at it one by one

// Then /catch methods are added to the instance of the promise
Promise.prototype = {
    constructor: Promise.// The identifier that represents the promise refactored for ourselves
    selfWrite: true.then: function then (onFulfilled, onRejected) {
        // The essence of implementing a THEN chain is to return an instance of a promise, processing the passed method inside the new promise
        newPromise = function newPromise(){}
        return newPormise
    },
    catch: function catch (onRejected){}}Copy the code

then

To implement the THEN chain mechanism, we return a new Promise instance

then: function then (onFulfilled, onRejected) {

  var self = this.// Then the result of the argument function
    x,
    newPromise
  // Then is passed as a function
  if (typeofonFulfilled ! = ='function') {
    onFulfilled = function onFulfilled(result) {
      return result
    }
  }
  if (typeofonRejected ! = ='function') {
    onRejected = function onFulfilled(reason) {
      throw reason
    }
  }
  newPromise = new Promise(function (resolve, reject) {
      // 1. Know the state of the current PROMISE instance
      // 2. Add then events to the event pool if the state is pending
      switch(self.PromiseState){
          case 'fulfilled':
              // Wrap it as an asynchronous function
              setTimeout(function(){
                  try{
                      // Pass in the result of the previous Promise instance
                      x = onFulfilled(self.PromiseResult)
                      // Handle x, such that the passed x is a Promise instance
                      hanlde(newPromise, x, resolve, reject)
                  } catch(err) {
                      reject(err)
                  }
              }, 0)
              break;
          case 'rejected':
             setTimeout(function () {
                  try {
                    x = onRejected(self.PromiseResult)
                    handle(newPromise, x, resolve, reject)
                  } catch (err) {
                    reject(err)
                  }
              }, 0)
              break;
           default: 
           // If the status is unknown, it is directly added to the event pool for execution
           self.onFulfilledCallbacks.push(function (result) {
               try {
                    x = onFulfilled(result)
                    handle(newPromise, x, resolve, reject)
               } catch (err) {
                   reject(err)
               }
           })
           self.onRejectedCallbacks.push(function (reason) {
              try {
                x = onRejected(reason)
                handle(newPromise, x, resolve, reject)
              } catch (err) {
                reject(err)
              }
            })
      }
      // Return promise for subsequent then execution
     return newPromise
  })
}
Copy the code

Handle function

The handle function in the previous code handles the state of the current instance

// this is a big pity, onFulfilled, onRejected method returns the result processing
function handle(newPromise, x, resolve, reject){
// The execution result of the new instance and the inner function will become an infinite loop if it is the same instance
    if(newPromise === x ) throw new TypeError('Chaining cycle detected for promise')

    if( isPromise(x) ) {
        // If the result of the function execution is a Promise instance passing in the current two functions to be executed
        x.then(resolve, reject)
    }
    // If x is a normal value/function, the result is passed directly to the next instance
    resolve(x)
}
Copy the code

IsPromise function

Determine if the result of the current execution is a Promise instance

function isPromise (x) {
    if( x == null) return false
    if( /^(function|object)$/i.test(typeof x) ) {
        if( typeof x.then === 'function' ) {
            return true}}return false
}
Copy the code

catch

Catch is simply passing a failed function to THEN

catch: function (onRejected) {
      // Pass only the failed state function to THEN
      var self = this
      return self.then(null, onRejected)
    }
Copy the code

resolve(result) / reject(reason)

Promise instances can also be implemented with promise.resovle because the instance also has these two apis and is very simple to implement.

Promise.resolve = function resolve (value){
    // Return a new Promise instance with its status changed to Success
    return new Promise(function (resolve) ) {
        resolve(value)
    }
}
Copy the code
Promise.reject = function reject(value) {
    // Return a new Promise instance with its status changed to Success
    return new Promise(function (reject) {
      reject(value)
    })
  }
Copy the code

all(promises)

The all method is executed in such a way that a promise is in a successful state only if all the incoming promises are in a successful state

Promise.all = function all (promises) {
    let results = [],
        n = 0,
        newPromises
    // If the argument is not an iterable type
    if ( Array.isArray(promises) ) throw new TypeError(promises + 'is not iterable')
    // Make all the arguments inside the array a Promise instance
    promises = promises.map( function (promise) {
        if ( !isPromise(promise) ) return new Promise.resolve(promise)
        return promise
    }
    newPromise = new Promise(function (resolve, reject) {
        promises.forEach(function (promise, index) {
            promise.then(function (result) {
                // Each instance is required to be successful
                results[index] = result
                // In each promise instance, a function is executed that we have no way of determining if it is a synchronous function
                // Using push may cause confusion between the asynchronous function at the front of the array and the synchronous function at the back
                n++
                if(n >= promises.length) {
                    resolve(results)
                }
            }).catch(function (reason) {
                // If an instance goes to catch, it returns a failure
                reject(reason)
            })
        })
    })
    return newPromise
}
Copy the code

finally(callback)

Does a finally execution need to return an instance of promise, and any function passed in finally must be executed regardless of the state

  Promise._finally = function _finally(callback) {
    let self = this,
        p = new Promise
    return self.then(
      function Fulfill(value) { 
        // Manage callback functions
        p.resolve( callback() ).then( function () { return value } )
       },
      function reject(reason) { 
        p.resolve( callback() ).then( function () { throw reason } )
       }
    )
  }
Copy the code

If you feel that this module is very confusing, I suggest you go to my GitHub to pull the whole code oh

After the

Thank 😘


If you find the content helpful:

  • ❤️ welcome to focus on praise oh! I will do my best to produce high-quality articles

Contact author: Linkcyd 😁 Previous:

  • Talk about front-end performance optimization based on browser rendering mechanism

  • Scope and closures: The worst interview question, did you get it right?

  • JavaScript data type details: A simple way to understand concepts