Promise series

  1. Promise – Three questions of the soul
  2. Promise – Handwritten source code
  3. Promise – Fully functional

The previous article provided a brief look at the USE of the Promise API with some documentation and examples. In this article, we will use JavaScript to implement A simple program that complies with the Promise A+ protocol, based on some of the protocol points summarized in the previous article.

The source code to write

Implement a synchronous Promise framework

  • Promise is a class that has three states (pending, fulfilled, rejected)
  • When the Promise constructor is initialized, we pass executor, a function that will execute by default. We pass resolve/reject
  • The default state of Promise is Pending
  • When the executor arguments resolve/reject are called, the current state is modified and the success value/reason is recorded.
  • Only the pending state can be modified to depressing/Rejected. If the state is fulfilled/ Rejected, the current state cannot be modified
  • Each Priomise must provide a THEN method. By default, two callback methods are passed in, which is controlled by the program. OnFulfilled is called by default when the Fulfilled state is fulfilled and onRejected is called when the Fulfilled state is fulfilled
// Promise has three states: wait, success, and reject
const STATUS = {
  PENDING: 'pending'.FULFILLED: 'fulfilled'.REJECTED: 'rejected',}class Promise {
  constructor(executor) {
    // Promise defaults to Pending
    this.status = STATUS.PENDING
    this.value = undefined // Store success values for subsequent chain calls
    this.reason = undefined // Store the cause of the failure to facilitate subsequent chain calls
    // The user calls the resolve function to change the state of the current instance when executing the function correctly. The resolve function supports passing in an argument as a success value
    const resolve = (value) = > {
      // The promise state can be changed only in the wait state
      if (this.status === STATUS.PENDING) {
        // Change the instance status to successful state
        this.status = STATUS.FULFILLED
        // Accept the success value from the user
        this.value = value
      }
    }
    Reject is called by the user to modify the state of the current instance when an error occurs or an exception is thrown. Reject supports passing in a parameter as the cause of failure or rejection
    const reject = (reason) = > {
      // Promise can only be modified in the wait state
      if (this.status === STATUS.PENDING) {
        // Change the state of the instance to failed
        this.status = STATUS.REJECTED
        // Accept the failure cause sent by the user
        this.reason = reason
      }
    }
    try {
      // New Promise() passes in a function that executes by default and takes two executable method arguments
      executor(resolve, reject)
    } catch (error) {
      // If the default function fails, the state is set to reject
      reject(error)
    }
  }

  This is a big pity; /** * 1. Each Priomise must provide a THEN method, which will be called onFulfilled by default. OnFulfilled is a big pity, and onRejected is called * when this state is fulfilled@param {*} Ondepressing successfully performs the function *@param {*} OnRejected fails to execute the function */
  then(onFulfilled, onRejected) {
    if (this.status === STATUS.FULFILLED) {
      onFulfilled(this.value)
    }
    if (this.status === STATUS.FULFILLED) {
      onRejected(this.reason)
    }
  }
}
module.exports = Promise
Copy the code

We implemented a Promise base shelf, but as a class that exists primarily to handle asynchronous operations, it wouldn’t work if we added asynchrony at this point. Now let’s add asynchrony

Implement the asynchronous invocation of Promise

If the Executor method is asynchronous, we will save the onFulfilled and onRejected methods when the STATUS is pending in THEN, and then save the method call when the state is modified.

  constructor(executor){
    // ...
    // If the state does not change and then is called, we need to store the successful/failed methods in then and wait until the state changes
    this.onFulfilledCallbacks = [] // Then's set of successful methods
    this.onRejectedCallbacks = [] // Set of failed methods for then
    const resolve = (val) = > {
      if(this.status === STATUS.PENDDING) {
        // ...
        // After the state changes, the successful method set call then is initiated to implement asynchronous operation
        this.onFulfilledCallbacks.forEach((fn) = > fn())
      }
    }
    const reject = (reason) = > {
      if(this.status === STATUS.PENDDING) {
        ...
        // After the state changes, then failed method set calls are initiated to implement asynchronous operation
        this.onRejectedCallbacks.forEach((fn) = > fn())
      }
    }
    // ...
  }
  then(onFulfilled,onReject) {
    // ...
    // When the state of a promise is PENDING, it is an asynchronous operation
    // Publish the resolve and reject methods in publish-subscribe mode and invoke them when subsequent state changes are complete
    if (this.status === STATUS.PENDING) {
      // Use decorator pattern/slicing to wrap the success/failure methods so that we can do other operations and get value/ Reason after state changes
      this.onFulfilledCallbacks.push(() = > {
        onFulfilled(this.value)
      })
      this.onRejectedCallbacks.push(() = > {
        onRejected(this.reason)
      })
    }
  }
  // ...
Copy the code

At this point, we’ve basically implemented a simple Promise single then method call. But Promise’s main satisfence-enhancing then chain calls aren’t available yet, so let’s add the chain calls

Further implementation of the then() chain call

  1. Each Promise prototype has a THEN method on top, and the THEN method provides two executable method parameters
  2. The then method must return a promise, which we do by new a new promise
  3. Then Specifies the call rules for subsequent “THEN” in chain calls
    • If an exception is thrown while calling a current successful/failed method, it will go to the next failed method
    • Get the success/failure return value x
    • If x is the underlying data type, it goes into the successful method of the next THEN (the successful method of the then is called whether it succeeds or fails).
    • If x is an object
      • X is a generic object that goes straight to the successful method of the next THEN
      • X is a Promise, get the THEN of x, keep calling the THEN until you get the normal object or the underlying data type
        • The successful and failed methods of the THEN of the promise2 call the resolve/ Reject methods corresponding to the promise
        • The success or failure of the next THEN depends on the success or failure of X
  4. Subsequent chained calls to THEN will proceed to the next failure only if this exception is thrown or the Promise returned by the callback method fails. Otherwise, the next resolve method is used
then(onFulfilled, onRejected) {
    // Create a new promise2 and return it at the end of the method, then reaches the chain call, continuing then is actually calling the new promise instance
    const promise2 = new Promise((resolve, reject) = > {
      // When we call the then method, we call different pass-the-argument methods by determining the current Promise state
      // When the promise state is Fulfilled, the onFulfilled method is called and the success value is passed in as the parameter
      if (this.status === STATUS.FULFILLED) {
        // We need to handle promise2 and x, but promise2 is assigned after new, so we need to use an asynchronous macro/micro to get the promise
        setTimeout(() = > {
          try {
            const x = onFulfilled(this.value)
            // We use an external method to unify the processing here
            resolvePromise(x, promise2, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)}// When the promise status is REJECTED, call the onRejected method and pass the failure reason as an argument
      if (this.status === STATUS.REJECTED) {
        setTimeout(() = > {
          try {
            const x = onRejected(this.reason)
            // We use an external method to unify the processing here
            resolvePromise(x, promise2, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)}// When the state of a promise is PENDING, it is an asynchronous operation
      // Publish the resolve and reject methods in publish-subscribe mode and invoke them when subsequent state changes are complete
      if (this.status === STATUS.PENDING) {
        // Use decorator pattern/slicing to wrap the success/failure methods so that we can do other operations and get value/ Reason after state changes
        this.onFulfilledCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onFulfilled(this.value)
              // We use an external method to unify the processing here
              resolvePromise(x, promise2, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)})this.onRejectedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onRejected(this.reason)
              // We use an external method to unify the processing here
              resolvePromise(x, promise2, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)})}})return promise2
  }
}
Copy the code

The method handling for success or failure callback is the same in all states, so a tool method is provided for unified use

// Handle subsequent state changes to the Promise(promise2) returned in then
function resolvePromise(x, promise2, resolve, reject) {
  // If promise2 is passed in by itself
  if (x === promise2) {
    return reject(new TypeError('Repeat call'))}// Determine if the passed x is an object or method
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    // In order to be compatible with other promises, add an anti-repetition feature to prevent other promises from being called repeatedly
    let called
    Reject (x) if an exception is thrown
    try {
      // Get the then attribute of x
      const then = x.then
      // If then is function, we default x to Promise
      if (typeof then === 'function') {
        // Call the then method of x, and call the corresponding success and failure if x succeeds/fails internally
        then.call(
          x,
          (y) = > {
            if (called) return
            called = true
            // If y is still a Promise, then needs to be resolved again to know that it is not a Promise
            // resolve(y)
            resolvePromise(y, promise2, resolve, reject)
          },
          (r) = > {
            if (called) return
            called = true
            reject(r)
          }
        )
      } else {
        // If then is not a function, we default x to a non-promise object. Resolve (x)
        resolve(x)
      }
    } catch (error) {
      if (called) return
      called = true
      reject(error)
    }
  } else {
    Resolve (x) if the value passed is a primitive value type
    resolve(x)
  }
}
Copy the code

Handle empty arguments for THEN

This will be a big pity. This will be a big pity. This will be a big pity. {}) such code. We deal with this situation.

  then(onFulfilled, onRejected) {
    // If onFulfilled/onRejected cannot obtain the current success/failure value to the next THEN
    // New promise((resolve,reject) =>{resolve(1)}).then().then().then(of=> {console.log(of)}) needs to be able to pass in the value of
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data= > data
    onRejected = typeof onRejected === 'function' ? onRejected : e= > {throw e}
    // ...
  }
Copy the code

The resolve method passes in a Promise

If the consumer passes in a Promise when the constructor initializes, we need to use the success or failure of the Promise as the success/failure of our Promise

  constructor(executor) {
    / /...
    const resolve = (value) = > {
      // Determine if the value passed in is a Promise. If it is still a Promise, return the call to THEN directly, passing the success and failure callbacks of resolve/reject to then. If the Promise succeeds/fails, modify the current state of the Promise with the callback argument
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      / /...}}Copy the code

test

Use the Promise A+ link to find Promises – aplus-Tests and test this case. If successful, it means that our Promise meets the protocol standards and can be mixed

Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) = > {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
Copy the code

The final code

// Promise has three states: wait, success, and reject
const STATUS = {
  PENDING: 'PENDING'.FULFILLED: 'FULFILLED'.REJECTED: 'REJECTED',}class Promise {
  constructor(executor) {
    // Promise defaults to Pending
    this.status = STATUS.PENDING
    this.value = undefined // Store success values for subsequent chain calls
    this.reason = undefined // Store the cause of the failure to facilitate subsequent chain calls

    // If the state does not change and then is called, we need to store the successful/failed methods in then and wait until the state changes
    this.onFulfilledCallbacks = [] // Then's set of successful methods
    this.onRejectedCallbacks = [] // Set of failed methods for then

    // The user calls the resolve function to change the state of the current instance when executing the function correctly. The resolve function supports passing in an argument as a success value
    const resolve = (value) = > {
      // Determine if the value passed in is a Promise. If it is still a Promise, return the call to THEN directly, passing the success and failure callbacks of resolve/reject to then. If the Promise succeeds/fails, modify the current state of the Promise with the callback argument
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }

      // The promise state can be changed only in the wait state
      if (this.status === STATUS.PENDING) {
        // Change the instance status to successful state
        this.status = STATUS.FULFILLED
        // Accept the success value from the user
        this.value = value
        // After the state changes, the successful method set call then is initiated to implement asynchronous operation
        this.onFulfilledCallbacks.forEach((fn) = > fn())
      }
    }

    Reject is called by the user to modify the state of the current instance when an error occurs or an exception is thrown. Reject supports passing in a parameter as the cause of failure or rejection
    const reject = (reason) = > {
      // Promise can only be modified in the wait state
      if (this.status === STATUS.PENDING) {
        // Change the state of the instance to failed
        this.status = STATUS.REJECTED
        // Accept the failure cause sent by the user
        this.reason = reason
        // After the state changes, the successful method set call then is initiated to implement asynchronous operation
        this.onRejectedCallbacks.forEach((fn) = > fn())
      }
    }
    try {
      // New Promise() passes in a function that executes by default and takes two executable method arguments
      executor(resolve, reject)
    } catch (error) {
      // If the default function fails, the state is set to reject
      reject(error)
    }
  }

  /** * 1. Each Promise prototype has a THEN method on it that provides two executable method arguments *@param {*} Ondepressing successfully performs the function *@param {*} 2. The then method must return a promise. We do this by new a new promise *@returns New Promise() * 3. Then if the current successful/failed method is called, if an exception is thrown, then the next failed method will be used * + return the success/failure value x * + if x is an object * * + x is a Promise that obtains the THEN of X and continues to invoke the THEN until it succeeds in obtaining the normal object or base data type * + Successful and failed methods invoke the Promise in the promise2 THEN Resolve /reject methods * + The success or failure of the next THEN is determined by the success or failure of X * + If x is the base data type, the success method of the next THEN will be called (success or failure of the then method will be called) * If the Promise returned by the callback fails, then the next call will be resolved. If the Promise returned by the callback fails, then the next call will be resolved
  then(onFulfilled, onRejected) {
    // If onFulfilled/onRejected cannot obtain the current success/failure value to the next THEN
    // New promise((resolve,reject) =>{resolve(1)}).then().then().then(of=> {console.log(of)}) needs to be able to pass in the value of
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : (data) = > data
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (e) = > {
            throw e
          }

    // Create a new promise2 and return it at the end of the method, then reaches the chain call, continuing then is actually calling the new promise instance
    const promise2 = new Promise((resolve, reject) = > {
      // When we call the then method, we call different pass-the-argument methods by determining the current Promise state
      // When the promise state is Fulfilled, the onFulfilled method is called and the success value is passed in as the parameter
      if (this.status === STATUS.FULFILLED) {
        // We need to handle promise2 and x, but promise2 is assigned after new, so we need to use an asynchronous macro/micro to get the promise
        setTimeout(() = > {
          try {
            const x = onFulfilled(this.value)
            // We use an external method to unify the processing here
            resolvePromise(x, promise2, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)}// When the promise status is REJECTED, call the onRejected method and pass the failure reason as an argument
      if (this.status === STATUS.REJECTED) {
        setTimeout(() = > {
          try {
            const x = onRejected(this.reason)
            // We use an external method to unify the processing here
            resolvePromise(x, promise2, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)}// When the state of a promise is PENDING, it is an asynchronous operation
      // Publish the resolve and reject methods in publish-subscribe mode and invoke them when subsequent state changes are complete
      if (this.status === STATUS.PENDING) {
        // Use decorator pattern/slicing to wrap the success/failure methods so that we can do other operations and get value/ Reason after state changes
        this.onFulfilledCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onFulfilled(this.value)
              // We use an external method to unify the processing here
              resolvePromise(x, promise2, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)})this.onRejectedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onRejected(this.reason)
              // We use an external method to unify the processing here
              resolvePromise(x, promise2, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)})}})return promise2
  }
}

// Handle subsequent state changes to the Promise(promise2) returned in then
function resolvePromise(x, promise2, resolve, reject) {
  // If promise2 is passed in by itself
  if (x === promise2) {
    return reject(new TypeError('Repeat call'))}// Determine if the passed x is an object or method
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    // In order to be compatible with other promises, add an anti-repetition feature to prevent other promises from being called repeatedly
    let called
    Reject (x) if an exception is thrown
    try {
      // Get the then attribute of x
      const then = x.then
      // If then is function, we default x to Promise
      if (typeof then === 'function') {
        // Call the then method of x, and call the corresponding success and failure if x succeeds/fails internally
        then.call(
          x,
          (y) = > {
            if (called) return
            called = true
            // If y is still a Promise, then needs to be resolved again to know that it is not a Promise
            // resolve(y)
            resolvePromise(y, promise2, resolve, reject)
          },
          (r) = > {
            if (called) return
            called = true
            reject(r)
          }
        )
      } else {
        // If then is not a function, we default x to a non-promise object. Resolve (x)
        resolve(x)
      }
    } catch (error) {
      if (called) return
      called = true
      reject(error)
    }
  } else {
    Resolve (x) if the value passed is a primitive value type
    resolve(x)
  }
}

Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) = > {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise
Copy the code

The code address

code