Promise base correlation

The Promise object is used to represent the final completion (or failure) of an asynchronous operation and its resulting value. Promise-Javascript | MDN

Here is the specification document for promise the Promise A+ specification

Promise is a common knowledge in front-end work, study and interview. It is not enough for me to use it skillfully, so it is necessary to realize it in person. Let’s start!!

Manual implementation

Implementation approach

  1. The Promise accepts an immediate function that takes two callback arguments, resolve,reject
  2. A promise has three states: Pending, depressing, and Rejected. And it can only change from pending to the other two states
  3. Proise’s then method takes the changed promise state and returns a new Promise object
  4. The implementation of the callback value handler function in the then method
  5. Promise’s static methods, resolve, Reject, Race,all

Create a new TjfPromise class, pass in the callback argument and execute it immediately

// Define the three states of promise
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class TjfPromise{
    constructor(callback){
        this.status = PENDING // The initial state is pending
        this.value = null
        this.reason = null
        try{
            // The function passed in the promise is executed immediately. Note the this reference in the call
            // The resolve,reject functions in promise call internally implemented functions
            callback(this.resolve.bind(this), this.reject.bind(this))}catch(error){
            this.reject(error)
        }
    }
    // The promise state can only be fulfilled pending --> depressing or pending --> Rejected
    // When the resolve method is called, the status becomes depressing
    resolve(value){
        if(this.status === PENDING){
            this.status = FULFILLED
            this.value = value
        }
    }
    When the reject method is called, status changes to Rejected
    reject(reason){
        if(this.status === PENDING){
            this.status = REJECTED
            this.reason = reason
        }
    }
}
Copy the code

Initial implementation of then method

class TjfPromise {...then(onFulfilled, onRejected){
        switch(this.status){
            case FULFILLED:
                onFulfilled(this.value)
                break
            case REJECTED:
                onRejected(this.reason)
                break}}}Copy the code

Test the current code with two small examples

new TjfPromise((resolve, reject) = > {
    resolve(11)
}).then(value= > console.log(value))
Copy the code

The results are printed out 11, indicating the current preliminary implementation.

There are three problems here. The first problem is that there is only a judgment about the fulfilled and rejected states in the then method. When the promise is pending, the code is not executed immediately, that is, the asynchronous code is not processed. Promise, after all, is designed primarily to handle asynchronous tasks.

The second problem is that when the THEN method is called multiple times (non-chained calls), the contents of the previous THEN may not have been executed and need to be stored

The third problem is that the THEN function supports chained calls. The Promise A+ specification states that then must return A Promise object

Let’s address the first issue, support for asynchronous calls

Then method support for asynchrony

class TjfPromise{
    constructor(callback){
        this.status = PENDING
        this.value = null
        this._status = PENDING // Add a variable to the status set and get
        this.reason = null
        // Add two new variables to store pending callback functions
        this.FULFILLED_CALLBACK = null
        this.REJECTED_CALLBACK = null
        try{
            callback(this.resolve.bind(this), this.reject.bind(this))}catch(error){
            this.reject(error)
        }
    }
    get status() {/ / add the get
        return this._status
    }
    set status(newStatus) {/ / the new set
        this._status = newStatus // use _status to prevent nested sets from falling into an infinite loop
        if(newStatus === FULFILLED){ // Call the stored callback immediately when the state changes
            this.FULFILLED_CALLBACK(this.value)
        }
        if(newStatus === REJECTED){
            this.REJECTED_CALLBACK(this.reason)
        }
    }
    resolve(value){
        if(this.status === PENDING){
            this.value = value // The assignment must precede the state change
            this.status = FULFILLED
        }
    }
    reject(reason){
        if(this.status === PENDING){
            this.reason = reason
            this.status = REJECTED            
        }
    }
    then(onFulfilled, onRejected){
        switch(this.status){
            case FULFILLED:
                onFulfilled(this.value)
                break
            case REJECTED:
                onRejected(this.reason)
                break
            casePENDING:// Pending stores the callback function until it is called
                this.FULFILLED_CALLBACK = onFulfilled
                this.REJECTED_CALLBACK = onRejected
        }
    }
}
Copy the code

Let’s test the current code

new TjfPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(11)},1000)
}).then((value) = > console.log(value))
Copy the code

If 11 is printed after a delay of one second, there is no problem. Let’s solve the second problem, the multiple calls to THEN

Then method support for multiple calls

We need to make some changes to the code we just described and store the callbacks in an array

class TjfPromise{
    constructor(){
        // Change the variable we just used to store the pending callback function into two arrays
        this.FULFILLED_CALLBACK_LIST = []
        this.REJECTED_CALLBACK_LIST = []
    }
    set status(newStatus) {// Make some changes in the setter
        this._status = newStatus
        if(newStatus === FULFILLED){ // Execute all saved callbacks in sequence
            this.FULFILLED_CALLBACK_LIST.forEach( callback= > callback(this.value))
        }
        if(newStatus === REJECTED){
            this.REJECTED_CALLBACK_LIST.forEach( callback= > callback(this.reason))
        }       
    }
    then(onFulfilled, onRejected){ // Change the then method
        switch(this.status){
            case FULFILLED:
                onFulfilled(this.value)
                break
            case REJECTED:
                onRejected(this.reason)
                break
            casePENDING:// Pending stores the callback function until it is called
                this.FULFILLED_CALLBACK_LIST.push(onFulfilled)
                this.REJECTED_CALLBACK_LIST.push(onRejected)
    }
}
Copy the code

Test the

const promise = new TjfPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(11)},1000)
})
promise.then( value= > console.log(1,value))
promise.then( value= > console.log(2,value))
promise.then( value= > console.log(3,value))
Copy the code

Print the result

1 11
2 11
3 11
Copy the code

No problem, the second problem is solved, and now comes the third and most troublesome problem, the return of the then function

Perfection of THEN method

The implementation here follows the promise A+ document, which is cumbersome

Determination of callback parameters in then method

The then method should be treated as a direct return function if the argument passed is not or does not pass a value

class TjfPromise{...then(onFulfiled, onRejected){
        // Process the parameters passed in
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
        const realOnFulfilled = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
    }
    isFunction(func){ // Add a function to verify whether it is a function
        return typeof func === 'function'}}Copy the code

The then method should return a promise

class TjfPromise{...then(onFulfilled, onRejected){
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
        const realOnRejected = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
        const promise2 = new TjfPromise((resolve, reject) = > {
            switch(this.status){
                case FULFILLED:
                    realOnFulfilled(this.value)
                    break
                case REJECTED:
                    realOnRejected(this.reason)
                    break
                casePENDING:// Pending stores the callback function until it is called
                    this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled)
                    this.REJECTED_CALLBACK_LIST.push(realOnRejected)
            }
        })
        return promise2
    }
}
Copy the code

Here we need to decide the value of realOnfulfilled. Here we start implementing the resolvePromise function

The then method will add the resolvePromise function, which will be processed with realOnFulfilled first

class TjfPromise{...then(onFulfilled, onRejected){
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
        const realOnRejected = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
        const promise2 = new TjfPromise((resolve, reject) = > {
            try{
                const x = realOnFulfilled(this.value)
                this.resolvePromise(promise2, x, resolve, reject) // Add the resolvePromise function
            }catch(error){
                reject(error)
            }
            switch(this.status){
                case FULFILLED:
                    realOnFulfilled(this.value)
                    break
                case REJECTED:
                    realOnRejected(this.reason)
                    break
                case PENDING:
                    this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled)
                    this.REJECTED_CALLBACK_LIST.push(realOnRejected)
            }
        })
        return promise2
    }
}
Copy the code

Start implementing the resolvePromise function

class TjfPromise{...resolvePromise(promise2, x, resolve, reject){
            // When the return value is promise2, return an error
        if( x === promise2) return new TypeError('The promise and the x refer to the same')
        // when x is a Tjfpromise object, its then method is called
        if( x instanceof TjfPromise){
            x.then(resolve, reject)
        } else {
            resolve(x)
        }
    }
}
Copy the code

Promise2 has not been initialized yet, so an error is reported. Resolve is initialized in the queueMicrotask and realOnRejected is also initialized

QueueMicrotask executes a resolvePromise

class TjfPromise{...then(onFulfilled, onRejected){
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
        const realOnRejected = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
        const promise2 = new TjfPromise((resolve, reject) = > {
            const fulfilledMicrotask = () = > {
                try{
                    queueMicrotask(() = > { // call in microtask
                        const x = realOnFulfilled(this.value)
                        this.resolvePromise(promise2, x, resolve, reject)
                    })
                }catch(error){
                    reject(error)
                }
            }
            const rejectedMicrotask = (() = > {
                try{
                    queueMicrotask(() = > { // call in microtask
                        const x = realOnRejected(this.reason)
                        this.resolvePromise(promise2, x, resolve, reject)
                    })
                }catch(error){
                    reject(error)
                }
            })
            switch(this.status){
                case FULFILLED:
                    fulfilledMicrotask()
                    break
                case REJECTED:
                    rejectedMicrotask()
                    break
                case PENDING:
                    this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
                    this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
            }
        })
        return promise2
    }
}
Copy the code

At this point, the content of then function is basically completed. The following contents of resolvePromise are improved according to the promise A+ specification

Improve the resolvePromise method

class TjfPromise{...resolvePromise(promise2, x, resolve, reject){
         if( x === promise2) return new TypeError('The promise and the x refer to the same')
         if( x instanceof TjfPromise){
            queueMicrotask(() = > { // Add another layer of microtasks, more consistent with the promise A+ call
                x.then( y= > {
                    this.resolvePromise(promise2, y, resolve, reject)
                }, reject)
            })
         } else if(typeof x === 'object' || this.isFunction(x)){
            if(x === null) resolve(x)
            let then = null
            try{
                then = x.then
            } catch(error){
                return reject(error)
            }
            if(this.isFunction(x.then)) {
                let called = false
                try{
                  then.call(x,(y) = > {
                    if(called) return
                    called = true
                    this.resolvePromise(promise2, y, resolve, reject)
                  }, (error) = > {
                    if(called) return
                    called = false
                    reject(error)
                  })
                }catch(error){
                  if(called) return
                  reject(error)
                }
              } else {
                resolve(x)
              }
            }else {
            resolve(x)
        }
    }
}
Copy the code

Now that the resolvePromise method is done, let’s test it out

const promise = new TjfPromise((resolve, reject) = > {
  resolve('success')})function other () {
  return new TjfPromise((resolve, reject) = >{
    resolve('other')
  })
}
promise.then(value= > {
  console.log(1)
  console.log('resolve', value)
  return other()
}).then(value= > {
  console.log(2)
  console.log('resolve', value)
})
Copy the code

Print the result

1
resolve success
2
resolve other
Copy the code

no problem

Implementation of static methods

Promise has a few static methods inside that can only be called through prmise.race(), not an instance, and we implement a few of them commonly used

Resolve method

The static resolve method simply returns a Promise object

class TjfPromise{...static resolve(value){
        if(value instanceof TjfPromise){
            return value
        }
        return new TjfPromise((resolve, reject) = > {
            resolve(value)
        })
    }
}
Copy the code

Reject method

Static Reject returns a Promise object with Reason

class TjfPromise{...static reject(reason){
        return new TjfPromise((resolve, reject) = > {
            reject(reason)
        })
    }
}
Copy the code

Race method

The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected.

class TjfPromise{
    static race(promiselist){
        return new TjfPromise((resolve, reject) = > {
            if(! promiselist.length)return resolve()
            promiselist.forEach( promiseitem= > TjfPromise.resolve(promiseitem).then( value= > resolve(value), reason= > reject(reason)))
            })
    }
}
Copy the code

All methods

Promise. all(iterable), which returns a promise instance with value as an array, and rejected if there is one

class TjfPromise{...static all(promiselist) {
        return new TjfPromise((resolve, reject) = > {
          let valuelist = [];
          let count = 0
          if(! promiselist.length)return resolve();
          promiselist.forEach((promiseitem, index) = > {
            TjfPromise.resolve(promiseitem).then(
              (value) = > {
                valuelist[index] = value
                count++
                if (count === promiselist.length) resolve(valuelist)
              },
              (reason) = >reject(reason) ); }); }); }}Copy the code

Test the

const promise1 = new TjfPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(22)},1000)})const promise2 = new TjfPromise((resolve, reject) = > {
  resolve(11)
})

TjfPromise.all([promise1, promise2]).then( value= > console.log(value), reason= > console.log(reason))
Copy the code

Print the result

[22.11]
Copy the code

No problem, so that’s all the implementation of PrMISE, with all the complete code attached

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class TjfPromise {
  constructor(callback) {
    this.status = PENDING;
    this.value = null;
    this._status = PENDING;
    this.reason = null;
    this.FULFILLED_CALLBACK_LIST = [];
    this.REJECTED_CALLBACK_LIST = [];
    try {
      callback(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error); }}get status() {
    return this._status;
  }
  set status(newStatus) {
    this._status = newStatus
    if (newStatus === FULFILLED) {
      this.FULFILLED_CALLBACK_LIST.forEach((callback) = > callback(this.value));
    }
    if (newStatus === REJECTED) {
      this.REJECTED_CALLBACK_LIST.forEach((callback) = > callback(this.reason)); }}resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED; }}reject(reason) {
    if (this.status === PENDING) {
      this.reason = reason;
      this.status = REJECTED; }}then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled)
      ? onFulfilled
      : (value) = > value;
    const realOnRejected = this.isFunction(onRejected)
      ? onRejected
      : (reason) = > {throw reason};
    const promise2 = new TjfPromise((resolve, reject) = > {
      const fulfilledMicrotask = () = > {
        try {
          queueMicrotask(() = > {
            const x = realOnFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          });
        } catch(error) { reject(error); }};const rejectedMicrotask = () = > {
        try {
          queueMicrotask(() = > {
            const x = realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          });
        } catch(error) { reject(error); }};switch (this.status) {
        case FULFILLED:
          fulfilledMicrotask();
          break;
        case REJECTED:
          rejectedMicrotask();
          break;
        case PENDING:
          this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
          this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask); }});return promise2;
  }
  resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2)
      return new TypeError("The promise and the x refer to the same");
    if (x instanceof TjfPromise) {
      queueMicrotask(() = > {
        x.then((y) = > {
          this.resolvePromise(promise2, y, resolve, reject);
        }, reject);
      });
    } else if (typeof x === "object" || this.isFunction(x)) {
      if (x === null) resolve(x);
      let then = null;
      try {
        then = x.then;
      } catch (error) {
        return reject(error);
      }
      if (this.isFunction(x.then)) {
        let called = false;
        try {
          then.call(
            x,
            (y) = > {
              if (called) return;
              called = true;
              this.resolvePromise(promise2, y, resolve, reject);
            },
            (error) = > {
              if (called) return;
              called = false; reject(error); }); }catch (error) {
          if (called) return; reject(error); }}else{ resolve(x); }}else{ resolve(x); }}static resolve(value) {
    if (value instanceof TjfPromise) {
      return value;
    }
    return new TjfPromise((resolve, reject) = > {
      resolve(value);
    });
  }
  static reject(reason) {
    return new TjfPromise((resolve, reject) = > {
      reject(reason);
    });
  }
  static race(promiselist) {
    return new TjfPromise((resolve, reject) = > {
      if(! promiselist.length)return resolve();
      promiselist.forEach((promiseitem) = >
        TjfPromise.resolve(promiseitem).then(
          (value) = > resolve(value),
          (reason) = > reject(reason)
        )
      );
    });
  }
  static all(promiselist) {
    return new TjfPromise((resolve, reject) = > {
      let valuelist = [];
      let count = 0
      if(! promiselist.length)return resolve();
      promiselist.forEach((promiseitem, index) = > {
        TjfPromise.resolve(promiseitem).then(
          (value) = > {
            valuelist[index] = value
            count++
            if (count === promiselist.length) resolve(valuelist)
          },
          (reason) = > reject(reason)
        );
      });
    });
  }
  isFunction(func) {
    return typeof func === "function"; }}Copy the code

Reference article:

Starting with a Promise interview question that kept me awake, I went into the details of implementing promises