Promise原理理解

95 阅读6分钟

1.基本结构:

const promise = new Promise(handler)

handler:(resolve, reject) => {}

promise.then(onFulfilled, onRejected).then(onFulfilled, onRejected)

2.状态:

promise存在三种状态

  • Pending
  • Fulfilled
  • Rejected

状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态。

handler函数包含 resolvereject 两个参数,它们是两个函数, Promise 的状态正是由这两个函数更改的。

3.构思源码需要注意的点:

handlerhandler必须是函数 需要做类型判断 非函数类型则抛出错误

resolve&reject:需要做两件事,一个是状态修改,另一个是值的传递,另外需要注意确保函数只能执行一次

resolve(val) {
    if(this.status !== 'PENDING') return // 确保只能执行一次
    this.status = 'FULFILLED' // 状态修改
    this.value = val // 收集传进来的值
}
reject(err) {
    if(this.status !== 'PENDING') return
    this.status = 'REJECTED'
    this.value = err
}

对于then以及其参数onFulfilledonRejected

  1. resolvereject执行后状态发生了改变才会执行then以及内部对应的onFulfilled或者onRejected
  2. 需要注意onFulfilledonRejected必须是函数,否则会被忽略
  3. 执行then后会返回一个promise实例,这个是链式调用的基础
  4. then中的onFulfilled或者onRejected如果有返回值,那么下一个then中的onFulfilled或者onRejected可以通过参数获取到上一个promise实例的返回值,没有返回值则参数为undefined
  5. onFulfilled或者onRejected有返回值又会分为两种情况
  • 如果返回值不是Promise :下一个promise的then中的回调会立即执行(状态为fulfilled),可以通过回调的参数获取返回值,这里可能不太好理解,请看下面的例子,这里我们说的回调其实也就是onFulfilled
let promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve()
    }, 1000)
})
promise2 = promise1.then(res => {
    console.log('promise1的状态变为fulfilled') // 1s后执行
    return '这是给promise2的'
})
promise2.then(res => {
    console.log(res) // 打印 这是给promise2的 在promise1的then执行完会立即执行
})
  • 如果返回值是Promise:这里很容易由理解误区,这里的这个返回值并不会作为链式调用所返回的下一个promise,也就是说下面的promise2其实并不是这个返回值,而是另外一个promise。但这个返回值状态的变化会直接影响链式调用所返回promise(也就是下面的promise2)的状态。(不理解可以后面的代码会解释)
let promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("这是传给promise1的");
    }, 1000);
});
let promise2 = promise1.then((res) => {
    console.log(res, "promise1的回调执行拉"); // 1s后执行
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("这是传给promise2的");
        }, 2000);
    });
}); 
promise2.then((res) => {
   console.log(res, "promise2的回调执行拉"); // 在promise1的then执行完2s后执行 打印 这是传给promise2的, promise2的回调执行拉
});

4.简易版Promise

class MyPromise{
    const isFunction = variable => typeof variable === 'function'
    constructor(handler) {
        // handler必须是函数 否则抛错
        if (!isFunction(handle)) {
      		throw new Error('MyPromise must accept a function as a parameter')
    	}
        
	// 首先声明变量用来存储状态以及传递的值
        this.status = 'PENDING'
        this.resolveValue = undefined
        this.rejectValue = undefined
        // 这两个数组后面解释其作用
        this.onFulfilledList = []
        this.onRejectedList = []
        
        // 定义resolve以及reject
        resolve(val) {
            if(this.status !== 'PENDING') return // 确保只能执行一次resolve或者reject
            this.status = 'FULFILLED' // 状态修改
            this.resolveValue = val // 收集传进来的值
	}
        reject(err) {
            if(this.status !== 'PENDING') return
            this.status = 'REJECTED'
            this.rejectValue = err
	}
        
        // 执行handler
        try{
            /*
             * 注意:易错点 这里必须通过bind对resolve和reject的this绑定为此promise实例
             * 否则在resolve和reject内部无法通过this获取到此promise实例的status和resolveValue/rejectValue
             */
            handler(this.resolve.bind(this), this.reject.bind(this))
        }catch(err){
            this.reject(err)
        }
    }
}

// 定义then
MyPromise.prototype.then(onFulfilled, onRejected) {
    switch(this.status){
        case 'FULFILLED':{
            onFulfilled(this.resolveValue)
        }
        case 'REJECTED':{
            onRejected(this.rejectValue)
        }
    }
}

5.then的完善

上述简易版只是最基本的promise雏形,下面将对其进行完善

完善点1:

如果handler中的resolve或者reject函数被包裹在异步方法中,会出现未执行resolve或者reject直接执行该promisethen方法的情况,而没有执行resolve或者rejectpromise的状态就不会改变,状态仍然是PENDING,我们需要对这种情况进行处理。(记得我们上面定义的两个数组么,要派上用场了)

修正如下:

// 新增两个变量用来存储回调函数
this.onFulfilledList = []
this.onRejectedList = []
// then的修正
MyPromise.prototype.then(onFulfilled, onRejected) {
    switch(this.status){
        case 'FULFILLED':{
            onFulfilled(this.resolveValue)
            break
        }
        case 'REJECTED':{
            onRejected(this.rejectValue)
            break
        }
        // 将回调都存储到回调数组里面
        case 'PENDING': {
            this.onFulfilledList.push(() => {
		onFulfilled(this.resolveValue)
            })
            this.onRejectedList.push(() => {
		onRejected(this.rejectValue)
            })
	}
    }
}
// resolve和reject的修正
resolve(val) {
    if(this.status !== 'PENDING') return // 确保只能执行一次resolve或者reject
    this.status = 'FULFILLED' // 状态修改
    this.resolveValue = val // 收集传进来的值
    // 执行回调
    this.onFulfilledList.forEach((cb) => {
	cb()
    })
}
reject(err) {
    if(this.status !== 'PENDING') return
    this.status = 'REJECTED'
    this.rejectValue = err
    // 执行回调
    this.onRejectedList.forEach((cb) => {
	cb()
    })
}
完善点2:

完成then的链式调用。这里逻辑有些复杂,请仔细看注释,多看几遍加深理解。

MyPromise.prototype.then(onFulfilled, onRejected) {
    // 返回新的promise实例以实现链式调用
    return promise = new MyPromise((resolve, reject) => {
    
        // 修正后的成功回调
        fixedOnfulfilled(value) {
            // 执行自己promise的回调看是否报错 如果报错直接修改下一个promise的状态为rejected
            try {
                // 判断自己promise的回调是否为函数 不是函数直接传递值给下一个promise
                if(!isFunction(onFulfilled)) {
                    resolve(value)
                }else {
                    let res = onFulfilled(value) // 执行自己的回调并收集返回值
                    // 判断返回值是否为promise实例
                    if(res instanceof MyPromise) {
                        /*
                        * 返回值是promise实例
                        * 这里需要让我们返回promise的状态和回调返回promise的状态相同
                        * 把我们返回promise的resolve和reject 当作回调返回promise状态改变后的回调函数即可
                        * 即res的状态变为fulfilled后执行我们返回promise的resolve 变为rejected后执行reject
                        */
                        res.then(resolve, reject)
                    }else{
                        // 返回值不是promise实例,直接将返回promise的状态改为fulfilled
                        resolve(value)
                    }
                }
            }catch(err) {
                // 直接将返回promise的状态改为rejected
		reject(err)
            }
        }
        
        // 修正后的失败回调
        fixedOnRejected(err) {
            // 执行自己promise的回调看是否报错 如果报错直接修改下一个promise的状态为rejected
            try {
                // 判断自己promise的回调是否为函数 不是函数直接传递值给下一个promise
                if(!isFunction(onRejected)) {
                    reject(err)
                }else {
                    let res = onRejected(err) // 执行自己的回调并收集返回值
                    // 判断返回值是否为promise
                    if(res instanceof MyPromise) {
                        // 返回值是promise
                        res.then(resolve, reject)
                    }else{
                        // 返回值不是promise
                        reject(err)
                    }
                }
            }catch(err) {
                // 修改下一个promise的状态为rejected
                reject(err)
            }
        }
        
        // 根据状态执行修正后的回调
        switch(this.status){
            case 'FULFILLED':{
                fixedOnfulfilled(this.resolveValue)
                break
            }
            case 'REJECTED':{
                fixedOnRejected(this.rejectValue)
                break
        	}
            // 将回调都存储起来
            case 'PENDING': {
                this.onFulfilledList.push(() => {
                    onFulfilled(this.resolveValue)
                })
                this.onRejectedList.push(() => {
                    onRejected(this.rejectValue)
                })
            }
    	}
    })
}
完善点3:

异步修正

我们在一个promise内部调用resolve后同步执行一行代码,可以发现这行代码执行优先度仍然高于resolve函数内部的代码。所以我们可以推断resolvereject内部代码并非同步代码。

resolve(val) {
    // 用setTimeOut包裹一层以实现异步
    setTimeOut(() => {
	if(this.status !== 'PENDING') return // 确保只能执行一次resolve或者reject
	this.status = 'FULFILLED' // 状态修改
	this.resolveValue = val // 收集传进来的值
    	// 执行回调
    	this.onFulfilledList.forEach((cb) => {
            cb()
    	})
    })
}
reject(err) {
    setTimeOut(() => {
	if(this.status !== 'PENDING') return
    	this.status = 'REJECTED'
    	this.rejectValue = err
    	// 执行回调
    	this.onFulfilledList.forEach((cb) => {
            cb()
    	})
    })
}

6.最终代码

class MyPromise{
    const isFunction = variable => typeof variable === 'function'
	constructor(handler) {
        if (!isFunction(handle)) {
            throw new Error('MyPromise must accept a function as a parameter')
    	}
	
        this.status = 'PENDING'
        this.resolveValue = undefined
        this.rejectValue = undefined
        this.onFulfilledList = []
        this.onRejectedList = []
        
        resolve(val) {
            setTimeOut(() => {
                if(this.status !== 'PENDING') return
		this.status = 'FULFILLED'
		this.resolveValue = val
    		this.onFulfilledList.forEach((cb) => {
                    cb()
    		})
            })
	}
	reject(err) {
            setTimeOut(() => {
		if(this.status !== 'PENDING') return
    		this.status = 'REJECTED'
    		this.rejectValue = err
    		this.onFulfilledList.forEach((cb) => {
                    cb()
    		})
            })
	}
        
        try{
            handler(this.resolve.bind(this), this.reject.bind(this))
        }catch(err){
            this.reject(err)
        }
    }
}

MyPromise.prototype.then(onFulfilled, onRejected) {
    return promise2 = new MyPromise((resolve, reject) => {
        fixedOnfulfilled(value) {
            try {
                if(!isFunction(onFulfilled)) {
                    resolve(value)
                }else {
                    let res = onFulfilled(value) 
                    if(res instanceof MyPromise) {
                        res.then(resolve, reject)
                    }else{
                        resolve(value)
                    }
                }
            }catch(err) {
                reject(err)
            }
        }
        
        fixedOnRejected(err) {
            try {
                if(!isFunction(onRejected)) {
                    reject(err)
                }else {
                    let res = onRejected(err)
                    if(res instanceof MyPromise) {
                        res.then(resolve, reject)
                    }else{
                        reject(err)
                    }
                }
            }catch(err) {
                reject(err)
            }
        }
        
        switch(this.status){
            case 'FULFILLED':{
                setTimeOut(() => {
                    fixedOnfulfilled(this.resolveValue)
                })
                break
            }		
            case 'REJECTED':{
                setTimeOut(() => {
                    fixedOnRejected(this.rejectValue)
        	})
        	break
            }
            case 'PENDING': {
        	this.onFulfilledList.push(() => {
                    onFulfilled(this.resolveValue)
        	})
        	this.onRejectedList.push(() => {
                    onRejected(this.rejectValue)
        	})
            }
        }
    })
}