Working people! For the soul! It’s the front end! This series is summed up in the process of learning in the road of big front attack. If there is something wrong in the article, I hope you can criticize and correct it and make progress with each other. Please indicate the source of reprint.

Hand tear Promise source code

We introduced the Promise in the last big front-end attack road (two)JS asynchronous programming, we not only want to use it, but also to understand how it is realized, so that our cerebellum is awake, with my footsteps!

1. Core logic analysis and implementation of Promise

  1. The first thing we need to know is that a Promise is a class, and when we use that class, we need to pass in an executor, and that executor will execute immediately.
  2. There are three states in a Promise: pending; Successful fuilfilled; Failure is rejected. The initial state is pending, and the wait state can be converted to success or failure. This state, once established, cannot be changed.
  3. A promise contains resolve and reject functions that change the state of the promise. This is a pity. ‘resolve’ is a pity. ‘reject’ is a pity.
  4. The promise prototype object has a then method that determines the state, and if the state is successful, the success callback is called. If the state is failed, the failure callback function is called.
  5. The then method takes two functions as arguments, a success callback that executes on success and a failure callback that executes on failure, passed in by promise’s resolve and Reject, respectively.
// myPromise.js
// Constant is defined for reuse and code prompts
const PENDING = 'pending' / / wait for
const FULFILLED = 'fulfilled' / / success
const REJECTED = 'rejected' / / fail
// Define a constructor
class MyPromise {
  constructor (executor) {
    // Executor is an executor that executes immediately upon entry, passing in the resolve and reject methods
    executor(this.resolve, this.reject)
  }

  // An attribute of the instance object, starting with wait
  status = PENDING
  // Value after success
  value = undefined
  // The cause of the failure
  reason = undefined

  // Resolve and reject
  If called directly, the normal function this refers to window or undefined
  // Use the arrow function to make this point to the current instance object
  resolve = value= > {
    // Check whether the state is waiting to prevent the program from running down
    if(this.status ! == PENDING)return
    // Change the status to successful
    this.status = FULFILLED
    // Save the value after success
    this.value = value
  }
  reject = reason= > {
    if(this.status ! == PENDING)return
    // Change the status to failed
    this.status = REJECTED
    // The cause of the save failure
    this.reason = reason
  }
  then (successCallback, failCallback) {
    // Determine the status
    if(this.status === FULFILLED) {
      // Call the successful callback and return the value
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      // Call the failed callback and return the reason
      failCallback(this.reason)
    }
  }
}

module.exports = MyPromise
Copy the code

So we’ve implemented a simple version of Promise, so let’s test it out!

//promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
   resolve('success')
   reject('err')
 })

 promise.then(value= > {
   console.log('resolve', value)
 }, reason= > {
   console.log('reject', reason)
 })
// Print resolve success
Copy the code

After the test, the above code can be executed normally, but we usually use Promise to handle requests asynchronously. We use setTimeout to simulate the asynchronous situation.

//promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
  // We expect a delay of 2 seconds before a successful callback is executed and the parameters are printed
  // But the result is nothing back to us
  // This is because the main thread executes immediately, setTimeout is asynchronous, and then executes immediately
  The state is pending, so all callbacks in the then state will not be executed
  setTimeout(() = > {
    resolve('success')},2000); 
 })

 promise.then(value= > {
   console.log('resolve', value)
 }, reason= > {
   console.log('reject', reason)
 })
Copy the code

To be able to handle this asynchronous processing, we need to tweak our promises

// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor (exector) {
    exector(this.resolve, this.reject)
  }
  status = PENDING
  value = undefined
  reason = undefined
  // Define a successful callback parameter
  successCallback = undefined
  // Define a failure callback parameter
  failCallback = undefined

  resolve = value= > {
    if(this.status ! == PENDING)return
    this.status = FULFILLED
    this.value = value
    // Determine if the successful callback exists and call it if it does
    this.successCallback && this.successCallback(this.value)
  }

  reject = reason= > {
    if(this.status ! == PENDING)return
    this.status = REJECTED
    this.reason = reason
    // Determine if the failed callback exists and invoke it if it does
    this.failCallback && this.failCallback(this.reason)
  }

  then (successCallback, failCallback) {
    if(this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    } else {
      / / wait for
      Success and failure callbacks are stored because the status is not known
      // Wait until the function succeeds or fails
      this.successCallback = successCallback
      this.failCallback = failCallback
    }
  }
}
module.exports = MyPromise
Copy the code

The general idea is that we add pending status when executing THEN, store our success and failure callbacks, and when resolve or reject is executed, determine whether a success or failure callback exists, and execute if it does. Now let’s execute promise.js and see if we can print it out after 2s

Don’t feel good cowhide Promise to achieve a sense of accomplishment! But let’s look at the other case.

//promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('success')},2000); 
 })

 promise.then(value= > {
   console.log(1)
   console.log('resolve1', value)
 })
 
 promise.then(value= > {
  console.log(2)
  console.log('resolve2', value)
})

promise.then(value= > {
  console.log(3)
  console.log('resolve3', value)
})
Copy the code

The result of this code execution is to print 3 and resolve3 success after a delay of 2 seconds, which is clearly not what we expected.

  • Then methods on the same Promise object can be called multiple times.
  • For synchronous callbacks, simply execute them; For asynchronous callbacks, we need to store the successful and failed callbacks in an array and wait until the state of the Promise object changes.

We continue to work on the code!

// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor (exector) {
    exector(this.resolve, this.reject)
  }
    
  status = PENDING
  value = undefined
  reason = undefined
  // Define an empty array for a successful callback
  successCallback = []
  // Define an empty array of failed callbacks
  failCallback = []

  resolve = value= > {
    if(this.status ! == PENDING)return
    this.status = FULFILLED
    this.value = value
    // Determine if the successful callback exists and call it if it does
    // Loop the callback array. Pop out the method in front of the array and call it directly
    // The shift method returns the first item of the array, changing the array
    // If the array is empty, all callbacks are executed
    while(this.successCallback.length) this.successCallback.shift()(this.value)
  }

  reject = reason= > {
    if(this.status ! == PENDING)return
    this.status = REJECTED
    this.reason = reason
    // Determine if the failed callback exists and invoke it if it does
    // Same as success
    while(this.failCallback.length) this.failCallback.shift()(this.reason)
  }

  then (successCallback, failCallback) {
    if(this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    } else {
      / / wait for
      // Store both successful and failed callbacks in an array
      this.successCallback.push(successCallback)
      this.failCallback.push(failCallback)
    }
  }
}

module.exports = MyPromise
Copy the code

After such a revamp, we go back to implement promise.js and find that the result is as we expected ~ of course, there is still a very important feature that is not implemented. What problem did we learn Promise to solve? Yeah, that callback hell problem that makes our brains ache and our eyes ache.

Chain calls to the then method solve the callback hell problem for us, so how do we implement chain calls?

  • Then methods that want to chain calls need to return a Promise object
  • The return value of the then method is used as an argument to the next THEN method
  • If we return a Promise object, we need to determine the state of the promise
// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor (exector) {
    exector(this.resolve, this.reject)
  }

  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []

  resolve = value= > {
    if(this.status ! == PENDING)return
    this.status = FULFILLED
    this.value = value
    while(this.successCallback.length) this.successCallback.shift()(this.value)
  }

  reject = reason= > {
    if(this.status ! == PENDING)return
    this.status = REJECTED
    this.reason = reason
    while(this.failCallback.length) this.failCallback.shift()(this.reason)
  }

  then (successCallback, failCallback) {
    The then method returns the first Promise object
    let promise2 = new Promise((resolve, reject) = > {
      if(this.status === FULFILLED) {
        // x is the return value of the previous Promise callback
        // Determine whether x is a normal value or a promise object
        // Call resolve directly if it is plain paper
        // If it is a Promise object, see the result returned by the Promise object
        // Call resolve or reject based on the result returned by the Promise object
        let x = successCallback(this.value)
        resolvePromise(x, resolve, reject)
      } else if (this.status === REJECTED) {
         let x = failCallback(this.reason)
         resolvePromise(x, resolve, reject)
      } else {
        this.successCallback.push(() = > {
            let x = successCallback(this.value)
            resolvePromise(x, resolve, reject)
            })
        this.failCallback.push(() = > {
            let x = failCallback(this.value)
            resolvePromise(x, resolve, reject)
            })
      }
    });
    return promise2
  }
}

function resolvePromise(x, resolve, reject) {
  // Determine if x is an instance object
  if(x instanceof MyPromise) {
    / / promise object
    // x.then(value => resolve(value), reason => reject(reason))
    // After simplification
    x.then(resolve, reject)
  } else{
    / / common values
    resolve(x)
  }
}

module.exports = MyPromise
Copy the code

Another thing we need to consider here is that if the then method returns its own promise object, the promise doll will happen, and we need to report an error when that happens.

// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor (exector) {
    exector(this.resolve, this.reject)
  }

  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []

  resolve = value= > {
    if(this.status ! == PENDING)return
    this.status = FULFILLED
    this.value = value
    while(this.successCallback.length) this.successCallback.shift()(this.value)
  }

  reject = reason= > {
    if(this.status ! == PENDING)return
    this.status = REJECTED
    this.reason = reason
    while(this.failCallback.length) this.failCallback.shift()(this.reason)
  }

  then (successCallback, failCallback) {
    The then method returns the first Promise object
    let promise2 = new Promise((resolve, reject) = > {
      if(this.status === FULFILLED) {
        // Because the new Promise has to be executed before promise2 is implemented, there is no pormise2 in the synchronization code.
        // This part of the code needs to be executed asynchronously
        setTimeout(() = > {
          let x = successCallback(this.value)
          // Then the return promise is the same as the original promise.
          // Determine if x is the same as promise2, so pass promise2 to resolvePromise
          resolvePromise(promise2, x, resolve, reject)
          }, 0);
      } else if (this.status === REJECTED) {
         // This part of the code needs to be executed asynchronously
         setTimeout(() = > {
         let x = failCallback(this.reason)
         resolvePromise(x, resolve, reject)
          }, 0);
      } else {
        this.successCallback.push(() = > {
            // This part of the code needs to be executed asynchronously
            setTimeout(() = > {
            let x = successCallback(this.value)
            resolvePromise(x, resolve, reject)
             }, 0);   
            })
        this.failCallback.push(() = > {
            // This part of the code needs to be executed asynchronously
            setTimeout(() = > {
            let x = failCallback(this.value)
            resolvePromise(x, resolve, reject)
            }, 0); }}}));return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // If it is equal, return itself, throws a type error, and returns
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if(x instanceof MyPromise) {
    x.then(resolve, reject)
  } else{
    resolve(x)
  }
}

module.exports = MyPromise
Copy the code

So far we’ve implemented the chained call to THEN, but we haven’t done anything to catch and handle errors in our Promise class.

We use try catch to catch and handle errors. If our actuator or subsequent THEN methods have errors, we need to change the Promise state to the Rejected state.

// myPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor (exector) {
    // Catch errors, reject if there are any
    try {
      exector(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }


  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []

  resolve = value= > {
    if(this.status ! == PENDING)return
    this.status = FULFILLED
    this.value = value
    // Async callback
    // There is no need to pass the value when it is called, because it has already been handled by the push below
    while(this.successCallback.length) this.successCallback.shift()()
  }

  reject = reason= > {
    if(this.status ! == PENDING)return
    this.status = REJECTED
    this.reason = reason
    // Async callback
    // There is no need to pass the value when it is called, because it has already been handled by the push below
    while(this.failCallback.length) this.failCallback.shift()()
  }

  then (successCallback, failCallback) {
    let promise2 = new Promise((resolve, reject) = > {
      if(this.status === FULFILLED) {
        setTimeout(() = > {
          Reject if an error is reported in the callback
          try {
            let x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)}else if (this.status === REJECTED) {
        setTimeout(() = > {
          Reject if an error is reported in the callback
          try {
            let x = failCallback(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)}else {
        // Handle asynchronous success error cases
        this.successCallback.push(() = > {
          setTimeout(() = > {
            Reject if an error is reported in the callback
            try {
              let x = successCallback(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)})this.failCallback.push(() = > {
          setTimeout(() = > {
            Reject if an error is reported in the callback
            try {
              let x = failCallback(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)})}});return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if(x instanceof MyPromise) {
    x.then(resolve, reject)
  } else{
    resolve(x)
  }
}

module.exports = MyPromise
Copy the code

Let’s do a little test

// promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
  // An asynchronous method
  setTimeout(() = >{
    resolve('succ')},2000)
})
 
 promise.then(value= > {
  console.log(1)
  console.log('resolve', value)
  return 'aaa'
}, reason= > {
  console.log(2)
  console.log(reason.message)
  return 100
}).then(value= > {
  console.log(3)
  console.log(value);
}, reason= > {
  console.log(4)
  console.log(reason.message)
})

/ / 1
// resolve succ
/ / 3
// aaa
Copy the code

In practice, the parameters in the then method are optional, we can pass one or no parameters, the implementation is actually very simple.

// myPromise.js
then (successCallback, failCallback) {
    If there is a callback, select the callback. If there is no callback, pass a function to pass the argument
    successCallback = successCallback ? successCallback : value= > value
    // Error functions also perform assignments, throwing error messages
    failCallback = failCallback ? failCallback : reason= > {throw reason}
    let promise2 = new Promise((resolve, reject) = >{... })... }// Simplify can also be written like this, using the default value of the parameter, if not, use the default value
then (successCallback = value= > value, failCallback = reason= > {throwA tiny} {...})Copy the code

So let’s test that out

// promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
  resolve('succ')
})
 
promise.then().then().then(value= > console.log(value))

// succ
Copy the code
// promise.js
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) = > {
  reject('err')
})
 
 promise.then().then().then(value= > console.log(value), reason= > console.log(reason))

// err
Copy the code

Ok, so we just tore Promise up and stuffed it. Let’s take a look at some common ways to implement promises.

2.Promise.all method implementation

The promise.all method is used to solve asynchronous concurrency problems. Click on the portal for details

Let’s analyze it first:

  • The all method receives an array of either normal values or promise objects
  • The order of worth in the array must be the order in which we get the results
  • The PROMISE return value is also a Promise object that can call the then method
  • If all of the values in the array are successful, then the callback is successful, and if one of the values is failed, then the callback is failed
  • If all is called directly from the class, then all must be a static method
//myPromise.js
static all (array) {
    // Result array
    let result = []
    / / counter
    let index = 0
    return new Promise((resolve, reject) = > {
      let addData = (key, value) = > {
        result[key] = value
        index ++
        // If the counter and array are the same length, then all elements are executed and the output is ready
        if(index === array.length) {
          resolve(result)
        }
      }
      // Iterate over the passed array
      for (let i = 0; i < array.lengt; i++) {
        let current = array[i]
        if (current instanceof MyPromise) {
          The promise object executes then, adds the value to the array if resolve, and returns reject if it is an error
          current.then(value= > addData(i, value), reason= > reject(reason))
        } else {
          // The normal values are added to the corresponding array
          addData(i, array[i])
        }
      }
    })
}
Copy the code

3.Promise. Race method implementation

The promise. all method returns only after all successes. The promise. race method returns only after one success or failure. Click on the portal for details

// Return as long as there is a success or failure
  static race(array) {
    let promise = new MyPromise((resolve, reject) = > {
      for (let i = 0; i < array.length; i++) {
        let curr = array[i];
        // MyPromise instance result processing
        if (curr instanceof MyPromise) {
          curr.then(resolve, reject);
        } else {
          // Non-myPromise instance processingresolve(curr); }}});return promise;
  }
Copy the code

4. Implementation of promise. resolve method

The resolve method returns a Promise object, if the argument is a Promise object, or if it is a normal value, generate a Promise object to return the value. Click on portal for details

// myPromise.js
static resolve (value) {
    // If it is a Promise object, return it directly
    if(value instanceof MyPromise) return value
    // Return a promise object if it is a value, and return the value
    return new MyPromise(resolve= > resolve(value))
}
Copy the code

5.Promise. Reject method implementation

Resolve is similar to the promise.resolve method implementation, but click on the portal for details

static reject(reason) {
    // If it is an instance of MyPromise, it returns directly
    if (reason instanceof MyPromise) return reason;
    // If it is MyPromise instance otherwise return MyPromise instance
    return new MyPromise((resolve, reject) = > reject(reason));
  }
Copy the code

6. Promise. Prototype. Finally the realization of () method

  • Finally executes whether the current final state succeeds or fails
  • We can call then after finally to get the result
  • This function is used on prototype objects
  • Click on portal for details
// myPromise.js
finally (callback) {
    // How do I get the current promise state, use the THEN method, and return callback anyway
    // The then method returns a promise object, so we return the result of the then call
    // We need to get a successful callback after the callback, so we need to return value too
    // Failed callback also throws a cause
    If callback were an asynchronous Promise object, we would wait for it to complete, so we would use the static resolve method
    return this.then(value= > {
      // Pass the promise returned after the callback call, execute the promise, and return value on success
      return MyPromise.resolve(callback()).then(() = > value)
    }, reason= > {
      // Call the then method after a failure, and return the reason for the failure.
      return MyPromise.resolve(callback()).then(() = > { throw reason })
    })
}
Copy the code

7. Promise. Prototype. The realization of the catch () method

  • The catch method is designed to catch all error callbacks to the Promise object
  • Call the then method directly, passing undefined in successful places and Reason in wrong places
  • A catch method is a method that acts on a prototype object
  • Click on portal for details
// myPromise.js
catch (failCallback) {
    return this.then(undefined, failCallback)
}
Copy the code

conclusion

Here we have implemented the core logic of Promise, and also implemented several common methods of Promise. In the process of implementation, some cases are simulated, such as using setTimeout to simulate microtasks. What is more important is that we should understand the internal realization principle and realization idea of Promise, so that we can have more ideas and solutions when we do the code in the future.

History article portal

  • Big front – end approach (a) functional programming
  • Big front-end attack road (two)JS asynchronous programming

reference

Pull hook big front end training camp