1. Build the overall structure first

Key points:

  • Pay attention to methods on function objects and prototype objects, which are methods on function objects and which are methods on prototype objects
/** * Custom Promise function module ** /

(function (params) {
  /** * Promise constructor * excutor: executor function, synchronous execution */
  function Promise(excutor) {}

  /** * The Promise prototype object's then() * specifies the success and failure callback function * returns a new Promise object */
  Promise.prototype.then = function(onResolved, onReject) {}/** * The catch() of the Promise prototype object * specifies the failed callback function * returns a new Promise */
  Promise.prototype.catch = function(onReject) {}/** * The resolve method of the Promise function object returns a Promise specifying the success of the result data, with a value */
  Promise.resolve = function (value) {}

  The reject method of the /** * Promise function object returns a Promise that the result data fails, with the value Reason */
  Promise.reject = function (reason) {}

  /** * The all method of the Promise function object returns a Promise only if all of them succeed, or if one of them fails */
  Promise.all = function (promiseArr) {}

  /** * The race method of the Promise function object returns a Promise whose result is determined by the result of the first completed Promise */
  Promise.race = function (promiseArr) {}


  // Expose the Promise function externally
  window.Promise = Promise}) (window)
Copy the code

2. Implement the constructor

Key points:

  • The constructor takes an executor function, which takes resolve and reject. The executor function executes immediately
  • Pre-declared variables: Status: controls state changes, data: stores data, callbacks, stores callback functions
  • Pre-declared functions: resolve() {} changed to success and reject() {}
/** * Promise constructor * excutor: executor function, synchronous execution */
  function Promise(excutor) {

    this.status = 'pending'
    this.data = undefined
    this.callbacks = [] // Store in {onResolved, onRejected} format, which is the parameter passed when the then function is called

    function resolve(value) {}

    function reject(reason) {}

    excutor(resolve, reason)
  }

Copy the code

2.1 Implement resolve() to change the state to a successful state

Key points:

  • Change the state from ‘Pending’ to ‘Resolved’
  • Store the value passed by the outside world
  • !!!!!!!!! Determine whether the resolved state has changed. If yes, the resolve() function is resolved for the first time
  • !!!!!!!!! If resolve is not executed, then will be executed synchronously. If resolve is not executed, then will be executed synchronously.
  • !!!!!!!!! If the callback function to be executed in the callback queue cannot be executed directly. If it is executed directly, the callback will be executed when the outside world executes resolve in new Promise, which violates the principle of asynchronous callback execution. Therefore, the callback function needs to be executed asynchronously and setTimeout is used to simulate the asynchronous queue
function resolve(value) {
  // If the current state is not pending, terminate
  if(this.status ! ='pending') return
  // Change the status to Resolved
  this.status = 'resolved'
  // Save value data
  this.data = value
  // If the callback function is to be executed, execute the callback immediately and asynchronously
  if(this.callbacks.length) {
    setTimeout(() = > { // Put the callback function in the macro task list to ensure asynchronous execution, (simulate queue)
      this.callbacks.forEach(callbacksObj= >{ callbacksObj.onResolved(value) }); }}})Copy the code

2.2 Implement the reject() function to change the state to a failed state

  • The implementation is basically similar to the resolve() function
function reject(reason) {
  // If the current state is not pending, terminate
  if(this.status ! ='pending') return
  // Change the status to Rejected
  this.status = 'rejected'
  // Save value data
  this.data = reason
  // If the callback function is to be executed, execute the callback immediately and asynchronously
  if(this.callbacks.length) {
    setTimeout(() = > { // Put the callback function in the macro task list to ensure asynchronous execution, (simulate queue)
      this.callbacks.forEach(callbacksObj= >{ callbacksObj.onRejected(reason) }); }}})Copy the code

2.3 Consider the case of a throw Error during execution of an executor function

  • Excutor (resolve, reject) is invalid because an error occurs during the execution of the excutor function, and the status changes from ‘pending’ to ‘rejected’

  • When a catch catches an error, it simply executes reject, changing the state

// Execute excutor immediately
try {
  excutor(resolve, reject)
} catch (error) {
  reject(error)
}

Copy the code

3. Implement the then method of the prototype object

Key points:

  • The then method is used to get the value of a promise’s changed state by specifying a success or failure callback
  • Parameter: Successfully calling the onResolved function and the onRejected function are executed asynchronously
  • Return value: Returns a new Promise object that guarantees the chained callback to THEN
  • As the return value is a promise and the chained call is guaranteed, there are three cases when onResolved and onRejected execute and pass the three cases to the next THEN:
    1. The callback throws an exception with the status changed to Rejected and returns error
    1. If the result of the callback execution is not a Promise object and the state changes to Resolved, value is returned
    1. If the result of the callback execution returns a Promise object, the state is determined by the promise, and the result is determined by the Promise

3.1 Consider input and output parameters

Promise.prototype.then = function(onResolved, onRejected) {
  return new Promise((resolve, reject) = > {
    // Change the state of then and tell the return value by calling the current promise's resolve, reject})}Copy the code

3.2 Consider the processing logic in the three states (Pending, Resolved and Rejected)

Key points:

  • A pending state is stored in a callback queue when either resolve or REJECT is executed asynchronously or before executing then.
  • Resolved state: Execute then when resolve is synchronized and the callback is executed immediately
  • The rejected state is reject. The then method is executed only after the reject function is synchronized. In this case, the callback function is executed immediately
Promise.prototype.then = function(onResolved, onRejected) {
  return new Promise((resolve, reject) = > {
    // Change the state of then and tell the return value by calling the current promise's resolve, reject

    const _this = this

    if (_this.status == PENDING) {
        // The current state is still pending, and the callback function is stored as a pending state
        _this.callbacks.push({
          onResolved,
          onRejected
        })
      }
      if (_this.status == RESOLVED) {
        setTimeout(() = > {// Note that callbacks are executed asynchronously
          onResolved()
        })
      }
      if (_this.status == REJECTED) {
        setTimeout(() = > {// Note that callbacks are executed asynchronously
          onRejected()
        })
      }
  })
}

Copy the code

The problem here is that the callback does not change the state of the returned PROMISE, nor does it return a value

3.3 Solve the problem of state change and return value of returned promise

Key points:

  • This means changing the state of the returned promise object through the resolve and Reject methods of new’s promise
  • Also pass value or Reason
  • Consider the three scenarios described above, namely the three that can occur when the outside world calls the THEN method: throws an exception, is a Promise object, is not a Promise object

This section uses the RESOLVED state as an example.

Promise.prototype.then = function(onResolved, onRejected) {
  
  const _this = this

  return new Promise((resolve, reject) = > {
    // Change the state of then and tell the return value by calling the current promise's resolve, reject

    if (_this.status == RESOLVED) {
      setTimeout(() = > {// Note that callbacks are executed asynchronously
        // Try catch catches the exception thrown and the result is rejected
        try {
          const result = onResolved(_this.data)
          if (result instanceof Promise) {
            // Return a promise object in the callback, and use the then method to determine whether the result succeeds or fails
            result.then(resolve, reject)
          } else {
            // Case 2: instead of returning a Promise object,
            // Change the state to Resolved via the resolve method of the returned promise and tell the next level that this is Resolved and the value is Result
            resolve(result)
          }
        } catch (error) {
          // Change the state to Rejected by returning the promise reject method and tell the next level that it is Rejected because of error
          reject(error) 
        }
      })
    }
  })
}

Copy the code

The REJECTED state is similar to RESOLVED:

Promise.prototype.then = function(onResolved, onRejected) {

  const _this = this

  return new Promise((resolve, reject) = > {
    // Change the state of then and tell the return value by calling the current promise's resolve, reject

    if (_this.status == REJECTED) {
      setTimeout(() = > {// Note that callbacks are executed asynchronously
        // Try catch catches the exception thrown and the result is rejected
        try {
          const result = onRejected(_this.data)
          if (result instanceof Promise) {
            // Return a promise object in the callback, and use the then method to determine whether the result succeeds or fails
            result.then(resolve, reject)
          } else {
            // Case 2: instead of returning a Promise object,
            // Change the state to Resolved via the resolve method of the returned promise and tell the next level that this is Resolved and the value is Result
            resolve(result)
          }
        } catch (error) {
          // Change the state to Rejected by returning the promise reject method and tell the next level that it is Rejected because of error
          reject(error) 
        }
      })
    }
  })
}

Copy the code

Extract common processing functions and optimize THEN methods:

Promise.prototype.then = function(onResolved, onRejected) {
  const _this = this

  return new Promise((resolve, reject) = > {
    // Change the state of then and tell the return value by calling the current promise's resolve, reject

    const handle = function (callback) {
      try {
        const result = callback(_this.data)
        if (result instanceof Promise) {
          result.then(resolve, reject)
        } else {
          resolve(result)
        }
      } catch (error) {
        reject(error)
      }
    }

    if (_this.status == RESOLVED) {
        setTimeout(() = > {// Note that callbacks are executed asynchronously
          handle(onResolved)
        })
      }

    if (_this.status == REJECTED) {
      setTimeout(() = > {// Note that callbacks are executed asynchronously
        handle(onRejected)
      })
    }
  })
}

Copy the code

The PENDING state is not considered. The current PENDING logic is to store the callback function, so when will it be executed? The callback has been stored in the destroy queue and is waiting to be executed when resolve or Reject is executed. So the successful callback and the failed callback in THEN will be executed sooner or later, just at different times and then as soon as it’s executed, you need to take into account the state and the value of the promise that’s returned

if (_this.status == PENDING) {
  // The current state is still pending, and the callback function is stored as a pending state
  _this.callbacks.push({
    onResolved: () = > {
      handle(onResolved)
    },
    onRejected: () = > {
      handle(onRejected)
    }
  })
}

Copy the code

The main logic is to encapsulate the function Handle to deal with the state change and value of the returned promise, and to deal with the logic in three states respectively

Promise.prototype.then = function(onResolved, onRejected) {
  const _this = this

  /** * then (onResolved, onRejected) If rejected is rejected, error * 2 is returned. If it is not a Promise object and the state becomes Resolved, return value * 3. If it's a Promise object, the state is determined by the promise, and the outcome is determined by the promise */
  /** * Handle the onResolved (onRejected) then function because it returns a promise * Promise will change the state. Use the resolve and reject of new's promise to change the state of the returned Promise object */
  return new Promise((resolve, reject) = > {

    // The wrapper handles three cases when a callback is executed so that the returned Promise object changes state correctly
    const handle = function (callback) {
      try {
        const result = callback(_this.data)
        if (result instanceof Promise) {
          result.then(resolve, reject)
        } else {
          resolve(result)
        }
      } catch (error) {
        reject(error)
      }
    }
    if (_this.status == PENDING) {
      // The current state is still pending, and the callback function is stored as a pending state
      _this.callbacks.push({
        onResolved: () = > {
          handle(onResolved)
        },
        onRejected: () = > {
          handle(onRejected)
        }
      })
    }
    if (_this.status == RESOLVED) {
      setTimeout(() = > {
        handle(onResolved)
      })
    }
    if (_this.status == REJECTED) {
      setTimeout(() = > {
        handle(onRejected)
      })
    }
  })
}

Copy the code

3.4 Finally, deal with the abnormal situation of parameters

/** * handle parameter exceptions */
onResolved = typeof onResolved == 'function' ? onResolved : v= > v
onRejected = typeof onRejected == 'function' ? onRejected : reason= > { throw reason }
Copy the code

If there is reason => {throw reason}, this will do exception penetration

Then method complete version

Promise.prototype.then = function(onResolved, onRejected) {
  const _this = this

  /** * handle parameter exceptions */
  onResolved = typeof onResolved == 'function' ? onResolved : v= > v
  onRejected = typeof onRejected == 'function' ? onRejected : reason= > { throw reason }

  return new Promise((resolve, reject) = > {

    // The wrapper handles three cases when a callback is executed so that the returned Promise object changes state correctly
    const handle = function (callback) {
      try {
        const result = callback(_this.data)
        if (result instanceof Promise) {
          result.then(resolve, reject)
        } else {
          resolve(result)
        }
      } catch (error) {
        reject(error)
      }
    }
    if (_this.status == PENDING) {
      // The current state is still pending, and the callback function is stored as a pending state
      _this.callbacks.push({
        onResolved: () = > {
          handle(onResolved)
        },
        onRejected: () = > {
          handle(onRejected)
        }
      })
    }
    if (_this.status == RESOLVED) {
      setTimeout(() = > {
        handle(onResolved)
      })
    }
    if (_this.status == REJECTED) {
      setTimeout(() = > {
        handle(onRejected)
      })
    }
  })
}

Copy the code

4. Implement the catch method of the prototype object

Key points:

  • The onRejected callback specifies the failed callback function
  • Parameter: Returns a new Promise
Promise.prototype.catch = function(onReject) {
  return this.then(undefined, onReject) // A successful callback must be specified, even if undefined
}
Copy the code

5. Implement the resolve method of the function object

Key points:

  • The input parameter: value, which can be a basic type or a Promise object
  • Reference:
    • Returns a promise specifying success or failure of the resulting data
    • If it is a basic type, the status changes to Resolved and the return value is value
    • If it is a Promise object, the result of the promise object’s execution is returned
/** * The resolve method of the Promise function object returns a Promise specifying the success of the result data, with a value */
Promise.resolve = function (value) {
  return new Promise((resolve, reject) = > {
    if (value instanceof Promise) {
      value.then(resolve, reject)
    } else {
      resolve(value)
    }
  })
}
Copy the code

6. Implement the Reject method on the function object

Key points:

  • Input parameter: reason, the specific return reason, basic type
  • Reference:
    • Return a promise specifying that the resulting data is a failure
The reject method of the /** * Promise function object returns a Promise that the result data fails, with the value Reason */
Promise.reject = function (reason) {
  return new Promise((resolve, reject) = > {
    reject(reason)
  })
}
Copy the code

7. Implement the all method of function objects

Key points:

  • Function: wait for multiple asynchronous functions to complete before returning
  • Input: an array of promise objects
  • Output parameter: Returns a promise, only if all of them succeed, or if only one of them fails
Promise.all = function (promiseArr) {
  let resolvedCount = 0
  const values = new Array(promiseArr.length)
  return new Promise((resolve, reject) = > {
    promiseArr.forEach((p,index) = > {
      Promise.resolve(p).then(
        value= > {
          resolvedCount++
          values[index] = value
          if (resolvedCount == promiseArr.length) {
            resolve(values)
          }
        },
        reason= > {
          // Execute reject whenever there is a failure
          reject(reason) 
        }
      )
    })
  })
}

Copy the code

The problem with this approach is that when a failed promise is executed reject, the loop is not broken and subsequent successful callbacks are executed, but resolvedCount and promisearr.length are not equal.

8. Implement the Race method of the function object

/** * The race method of the Promise function object returns a Promise whose result is determined by the result of the first completed Promise */
Promise.race = function (promiseArr) {
  return new Promise((resolve, reject) = > {
    promiseArr.forEach((p, index) = > {
      Promise.resolve(p).then(resolve, reject)
    })
  })
}
Copy the code