We’re used to promises, but how does it work? So let’s analyze it.

According to Teacher Ruan YifengIntroduction to ES6″, for the Promise. As we can see, promises have two major features:



Based on our use of promises and features, we concluded the following points:

1. Promise is an asynchronous operation.

2. Promise has three states (pending, depressing and Rejected). The initial state is pending, and the other two states are the result states.

3. If the state has changed, then the result is returned immediately.

4. The Promise constructor accepts a function that takes resolve and reject.

5. Promise uses then to add multiple callback functions that trigger the execution of those state callbacks when the state changes.

6. Then takes two arguments, one for a successful callback and one for a failed callback, and the value of the resulting state goes into the callback.

7. Promises catch internal exceptions.

Through the above summary, we can preliminarily realize a Promise skeleton.

const STATUS = Object.freeze({ // Define three states
    PENDING: "pending".FULFILLED: "fulfilled".REJECTED: "rejected"
})
class MyPromise {
    _status = STATUS.PENDING; // Initial state
    _value = undefined;  // Execution result
    _fulfilledFun = []; // Depressing state corresponds to the task queue to be implemented
    _rejectedFun = [] // Rejected Indicates the task queue
    constructor(handle) {
        if (!this._isFunction(handle)) {
            throw TypeError('arguments must be a function')}try { // Promises catch internal execution exceptions
            handle(this._resolve.bind(this), this._reject.bind(this)) // The Promise constructor callback takes two arguments
        } catch (err) {
            this._reject(err)
        }

    }
    _isFunction(handle) {
        return typeof handle === 'function'
    }
    _run(tasks) {
        let cb;
        while (cb = tasks.shift()) {
            cb()
        }
    }
    _resolve(val) {
        if (this._status ! == STATUS.PENDING)return;
        this._status = STATUS.FULFILLED;
        this._value = val;
        setTimeout(() = > { /// In order to simulate asyncracy and achieve the same asyncracy effect as promise, this is actually a macro queue, not a real Promise microqueue
            this._run(this._fulfilledFun)
        }, 0)}_reject(val) {
        if (this._status ! == STATUS.PENDING)return;
        this._status = STATUS.REJECTED;
        this._value = val;
        setTimeout(() = > {
            this._run(this._rejectedFun)
        }, 0)}then(resolve, reject) {
        const { _status, _value } = this;
        switch (_status) {
            // Add multiple callbacks to the corresponding status task queue
            case STATUS.PENDING:
                this._fulfilledFun.push(resolve)
                this._rejectedFun.push(reject)
                break;
            // Return the result immediately if the status changes
            case STATUS.FULFILLED: 
                resolve(_value)
                break;
            case STATUS.REJECTED:
                reject(_value)
                break; }}}var p = new MyPromise((res, rej) = > {
    res(3333)
})
p.then((res) = > {
    console.log(1, res)
})
console.log(1)
// Execution result
/ / 1
/ / 1, 3333
Copy the code

Above we implemented a basic Promise skeleton.



As you can see in the figure, then returns a Promise instance, but we did not return anything from the previous step, so we need to modify then and extend then.

class MyPromise {
  	/ /...
  	// Is used to process the return value of then
  	_handleCheck(preTask, nextTask, nextResolve, nextReject) {
        let result = this._value;
        try {
            if (this._isFunction(preTask)) {
                result = preTask(result)
                if (result instanceof MyPromise) { // If then returns a promise, the next chained call uses the return value of the PROMISE in then
                    result.then(nextResolve, nextReject)
                } else {
                    nextTask(result)
                }
            } else { // If then is not a function, then the next chained call to then takes the value received by then
                nextTask(result)
            }
        } catch (err) {
            nextReject(err)
        }
    }
    then(resolve, reject) {
        const { _status } = this;
        return new MyPromise((nextResolve, nextReject) = > {
          	// Queue preprocessing, which is used to determine the result of then, in order to ensure that the next level of the chain call is passed correctly
            const _fulfilledHandle = this._handleCheck.bind(this, resolve, nextResolve, nextResolve, nextReject)
            const _rejectedHandle = this._handleCheck.bind(this, reject, nextReject, nextResolve, nextReject)
            
            switch (_status) {
                case STATUS.PENDING:
                    this._fulfilledFun.push(_fulfilledHandle)
                    this._rejectedFun.push(_rejectedHandle)
                    break;
                case STATUS.FULFILLED:
                    _fulfilledHandle()
                    break;
                case STATUS.REJECTED:
                    _rejectedHandle()
                    break; }}}})var p = new MyPromise((res, rej) = > {
    res(3333)
})


p.then((res) = > {
    console.log(1, res)
    return 67
}).then((res) = > {
    console.log(5, res)
})
/** * Result 1 1 3333 5 67 */
Copy the code

Such a basic core function has been implemented. Promise has a few other methods that we can implement with these basic features.

1, catch

catch(reject) {
    return this.then(undefined, reject)
}
Copy the code

2, the finally



finally(callback) {
  return this.then(
    value= > MyPromise.resolve(callback()).then(() = > value),
    error= > MyPromise.resolve(callback()).then(() = > {throw error})
  )
}
Copy the code

3, all

static all(list) {
  return new MyPromise((res, rej) = > {
    const values = [];
    const size = list.length;
    const count = 0;
    for(let [index, p] of list.entries() ){
      MyPromise.resolve(p).then(v= > {
        values[index] = v;
        count++;
        if(count === size) res(values)
      }).catch(err= > rej(err))
    }
  })
}
Copy the code

4, race

static race(list) {
  return new MyPromise((res, rej) = > {
    for(let p of list) {
      // Use myPromise.resolve instead of direct p.chen in case p is not a normal promise
      MyPromise.resolve(p).then(result= > {
        res(result)
      }, error= > rej(error))
    }
  })
}
Copy the code

5, allSettled

static allSettled(list) {
  const values = [];
  const size = list.length;
  let count = 0;
  return new MyPromise((res, rej) = > {
    const getValues = (val, index) = > {
      values[index] = val;
      count++;
      if(count === size) res(values)
    }
    for(let [index, p] of list.entries()) {
      MyPromise.resolve(p).then(result= > {
        getValues(result, index)
      }, error= > getValues(error, index))
    }
  })
}
Copy the code

6, any

static any(list) {
  const values = [];
  const size = list.length;
  const count = 0;
  return new MyPromise((res, rej) = > {
    for(let [index, p] of list.entries()) {
      MyPromise.resolve(p).then(result= > {
        res(result)
      }, error= > {
        values[index] = error;
        count++;
        if(count === size) rej(values)
      })
    }
  })
}
Copy the code

Below is the complete code implementation.

const STATUS = Object.freeze({
    PENDING: "pending".FULFILLED: "fulfilled".REJECTED: "rejected"
})
class MyPromise {
    _status = STATUS.PENDING;
    _value = undefined;
    _fulfilledFun = [];
    _rejectedFun = []
    constructor(handle) {
        if (!this._isFunction(handle)) {
            throw TypeError('arguments must be a function')}try {
            handle(this._resolve.bind(this), this._reject.bind(this))}catch (err) {
            this._reject(err)
        }

    }
    _isFunction(handle)  {
        return typeof handle === 'function'
    }
    _run(tasks) {
        let cb;
        while (cb = tasks.shift()) {
            cb()
        }
    }
    _resolve(val) {
        if (this._status ! == STATUS.PENDING)return;
        this._status = STATUS.FULFILLED;
        this._value = val;
        setTimeout(() = > { /// In order to simulate asyncracy and achieve the same asyncracy effect as promise, this is actually a macro queue, not a real Promise microqueue
            this._run(this._fulfilledFun)
        }, 0)}_reject(val) {
        if (this._status ! == STATUS.PENDING)return;
        this._status = STATUS.REJECTED;
        this._value = val;
        setTimeout(() = > {
            this._run(this._rejectedFun)
        }, 0)}_handleCheck(preTask, nextTask, nextResolve, nextReject) {
        let result = this._value;
        try {
            if (this._isFunction(preTask)) {
                result = preTask(result)
                if (result instanceof MyPromise) {
                    result.then(nextResolve, nextReject)
                } else {
                    nextTask(result)
                }
            } else {
                nextTask(result)
            }
        } catch (err) {
            nextReject(err)
        }
    }
    
    then(resolve, reject) {
        const { _status } = this;
        return new MyPromise((nextResolve, nextReject) = > {
            const _fulfilledHandle = this._handleCheck.bind(this, resolve, nextResolve, nextResolve, nextReject)
            const _rejectedHandle = this._handleCheck.bind(this, reject, nextReject, nextResolve, nextReject)
            switch (_status) {
                case STATUS.PENDING:
                    this._fulfilledFun.push(_fulfilledHandle)
                    this._rejectedFun.push(_rejectedHandle)
                    break;
                case STATUS.FULFILLED:
                    _fulfilledHandle()
                    break;
                case STATUS.REJECTED:
                    _rejectedHandle()
                    break; }})}catch(reject) {
        return this.then(undefined, reject)
    }
    finally(callback) {
        return this.then(
            value= > MyPromise.resolve(callback()).then(() = > value),
            error= > MyPromise.resolve(callback()).then(() = > {throw error})
        )
    }
    static resolve(value) {
        if (value instanceof MyPromise) return value;
        return new MyPromise(resolve= > resolve(value))
    }
    static reject(value) {
        if (value instanceof MyPromise) return value;
        return new MyPromise((resolve, reject) = > reject(value))
    }
    static all(list) {
        return new MyPromise((res, rej) = > {
            const values = [];
            const size = list.length;
            const count = 0;
            for(let [index, p] of list.entries() ){
                MyPromise.resolve(p).then(v= > {
                    values[index] = v;
                    count++;
                    if(count === size) res(values)
                }).catch(err= > rej(err))
            }
        })
    }
    static race(list) {
        return new MyPromise((res, rej) = > {
            for(let p of list) {
                // Use myPromise.resolve instead of direct p.chen in case p is not a normal promise
                MyPromise.resolve(p).then(result= > {
                    res(result)
                }, error= > rej(error))
            }
        })
    }
    static allSettled(list) {
        const values = [];
        const size = list.length;
        let count = 0;
        return new MyPromise((res, rej) = > {
            const getValues = (val, index) = > {
                values[index] = val;
                count++;
                if(count === size) res(values)
            }
            for(let [index, p] of list.entries()) {
                MyPromise.resolve(p).then(result= > {
                    getValues(result, index)
                }, error= > getValues(error, index))
            }
        })
    }
    static any(list) {
        const values = [];
        const size = list.length;
        const count = 0;
        return new MyPromise((res, rej) = > {
            for(let [index, p] of list.entries()) {
                MyPromise.resolve(p).then(result= > {
                    res(result)
                }, error= > {
                    values[index] = error;
                    count++;
                    if(count === size) rej(values)
                })
            }
        })
    }
}
Copy the code