ES6 Promise源碼解析 (從Promise功能的角度看Promise源碼實現)

上一篇,我們說了Promise的用法詳解。今天趁週末有空,我們來繼續看一看Promise源碼。下面,我將從Promise功能的角度一步一步來看每步的功能是怎麼實現的

首先,我們再來回顧一下Promise的基本用法,看代碼

new Promise((resolve, reject) => {
   
   
    try {
   
   
        if (success) {
   
   
            resolve()
        }
        if (fail) {
   
   
            reject()
        }
    } catch (err) {
   
   
        reject(err)
    }
}).then((res) => {
   
   
    console.log(res)
}, (err) => {
   
   
    console.log(err)
}).then((res) => {
   
   
    console.log(res)
}, (err) => {
   
   
    console.log(err)
}).catch((err => {
   
   
    console.log(err)
}))

我們先從上面的用法上,總結出一些Promise的特點

  1. 首先,毫無疑問,Promise()是一個構造函數, 並且,存在3種狀態,pending, fulfilled(也可以叫Resolved), rejected。分別表示等待時,成功時,失敗時
  2. Promise實例化時,傳了個參數,並且這個參數是個函數(並且是個立即執行函數),同時這個函數還有兩個參數,且這兩個參數,依然是函數,分別是resolve(), reject()
  3. then()函數中的第一個參數(後文我們統稱爲then的resolve回調),是在調用then()方法的Promise對象的狀態變爲fulfilled時被執行的,而第二個參數(後文我們統稱爲rejected回調),是在Promise對象的狀態變成rejected時被調用的。
  4. 通過第四代,我們還可以看出,在resolve()函數執行時,是將Promise對象的狀態變更成了fulfilled,從而觸發了then的resolve回調函數的執行。而reject()函數執行時,是將Promise對象的狀態變更成了rejected,從而觸發了then的reject回調的執行
  5. resolve(res)時的值,就是then的resolve回調的參數,reject(err)的值就是thenreject回調的參數
  6. then()函數和catch()函數可以被鏈式調用

1、Promise基礎雛形
2、then()函數的完善
3、Promise.then()的鏈式調用
4、Promise.catch()
5、Promise.resolve()
6、Promise.reject()
7、Promise.all()
8、Promise.race()






Promise基礎雛形

  1. 知曉了他是個構造函數,那我們就創建個構造函數,並定義好狀態,並立即執行實例化時傳入的函數
class MyPromise {
   
   
	constructor (fun) {
   
   
		 // 定義初始狀態(3個狀態分別是pending, fulfilled, rejected)
        this.status = 'pending'
        // 定義兩個變量分別來存儲成功時值和失敗時的值
        this.resolveValue = null
        this.rejectValue = null

		// 定義resolve函數
		let resolve = () => {
   
   }
		
		// 定義reject函數
		let reject = () => {
   
   }
		try {
   
   
			fun(resolve, reject)
		} catch (err) {
   
   
			reject(err)
		}
	}
}

此時,構造函數最基本的樣子已經有了,定義了一個狀態,執行了立即執行函數。並將resolve, reject傳入到立即執行函數中。下面我們來完善下 resolve, reject

let resolve = (val) => {
   
   
	// 1、將狀態變更爲fulfilled, 但是注意一點,Promise是有個特點的,就是狀態只能由pending狀態變更爲fulfilled或者由pending狀態變更爲rejected。且,狀態變化後,不會再變化。故,我們需要先判斷當前是否是等待狀態pending
	if (this.status === 'pending') {
   
     // this指向實例化出來Promise對象
		this.status = 'fulfilled'
		// 2、保存resolve時的值,以便後面調用then()方法時使用
		this.resolveValue = val
	}
}

let reject = (val) => {
   
   
	// 1、狀態變更爲rejected
	if (this.status === 'pending') {
   
   
		this.status = 'rejected'
		// 2、保存reject()時的值
		this.rejectValue = val
	}
}

此時,resolve, reject已經有了,new Promise()時,已經可以調用resolve()方法和reject()方法了。並且,resolve()和reject()時,promise狀態也已經發生了改變。並保存了resolve和reject出來的值。

  1. 在下一步,狀態已經發生改變了,我們是不是要觸發then的resolve回調,或者reject回調了。所以,我們來實現then()函數
MyPromise.prototype.then = (onFullFilled, onRejected) => {
   
   
	// onFullFilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
	// 此時,判斷狀態,不同狀態時,分別執行不同的回調
	if (this.status === 'fulfilled') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === 'rejected') {
   
   
		onRejected(this.rejectValue )
	}
	// 上面的this指向的是調用then的promise實例,故可以直接拿到狀態和返回值
}

此時,我們一條執行流程應該是走完了,下面,我們來測試下

class MyPromise {
   
   
    constructor (fun) {
   
   
		// 定義初始狀態(3個狀態分別是pending, fulfilled, rejected)
	    this.status = 'pending'
	    // 定義兩個變量分別來存儲成功時值和失敗時的值
	    this.resolveValue = null
	    this.rejectValue = null

		// 定義resolve函數
	    let resolve = (val) => {
   
   
	        // 1、將狀態變更爲fulfilled, 但是注意一點,Promise是有個特點的,就是狀態只能由pending狀態變更爲fulfilled或者由pending狀態變更爲rejected。且,狀態變化後,不會再變化。故,我們需要先判斷當前是否是等待狀態pending
	        if (this.status === 'pending') {
   
     // this指向實例化出來Promise對象
	            this.status = 'fulfilled'
	            // 2、保存resolve時的值,以便後面調用then()方法時使用
	            this.resolveValue = val
	        }
	    }
	    
	    // 定義reject函數
	    let reject = (val) => {
   
   
	        // 1、狀態變更爲rejected
	        if (this.status === 'pending') {
   
   
	            this.status = 'rejected'
	            // 2、保存reject()時的值
	            this.rejectValue = val
	        }
	    }
	
	    try {
   
   
	        fun(resolve, reject)
	    } catch (err) {
   
   
	        reject(err)
	    }
	}
}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
	// 此時,判斷狀態,不同狀態時,分別執行不同的回調
	if (this.status === 'fulfilled') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === 'rejected') {
   
   
		onRejected(this.rejectValue )
	}
	// 上面的this指向的是調用then的promise實例,故可以直接拿到狀態和返回值
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    resolve(123)
})
promise1.then((res) => {
   
   
    console.log(res)  // 123
}, (err) => {
   
   
    console.log(err)
})

輸出了123

then()函數的完善

到此時,只是完成了基本,但仍存在很多問題。大家發現沒有,我們在then函數中,只判斷了狀態爲fufilled時,調了onFullFilled,狀態爲rejected時,調了onRejected。但如果then()函數被調用時,promise的狀態還並未發生改變(也就是還處於pending時),那then()函數內的代碼是不是不會執行拉。因爲我們並沒有寫pending狀態時的處理代碼。如以下情況

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
}, (err) => {
   
   
    console.log(err)
})
沒有輸出

上面的代碼中,立即執行函數內是一個異步代碼,此時,是不是先執行了promise1.then(),然後才執行setTimeout中的回調函數啊。所以,此時,當promise1.then()執行時,狀態已經是pending,故不會有任何輸出。這就有問題了。而我們所希望的,是不是在resolve()或者reject()執行的時候,去觸發then()函數的resolve回調或者rejected回調執行啊。
那該怎麼做。我先說下思路。在then函數執行時,如果狀態還是pending的話,我們是不是可以先把then()的兩個回調函數先給他保存起來。然後在resolve()或者reject()的時候,再去觸發這個函數的調用呢。我們來寫一下
1、先定義兩個變量用來保存then()的回調函數

this.onFullFilledList = []
this.onRejectedList = []

2、then()執行時,如果狀態還未發生改變(還是pending時),那麼就將回調函數先保存起來

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
	// 此時,判斷狀態,不同狀態時,分別執行不同的回調
	if (this.status === 'fulfilled') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === 'rejected') {
   
   
		onRejected(this.rejectValue )
    }
    if (this.status === 'pending') {
   
   
    	// 保存的是一個函數,而函數內是回調的執行代碼,當我們執行被保存的函數時,函數內的onFullFilled和onRejected是不是也就跟着執行拉
        this.onFullFilledList.push(() => {
   
   
            onFullFilled(this.resolveValue)
        })
        this.onRejectedList.push(() => {
   
   
            onRejected(this.rejectValue )
        })
    }
	// 上面的this指向的是調用then的promise實例,故可以直接拿到狀態和返回值
}

3、在resolve()和reject()的時候, 去取onFullFilledList,onRejectedList兩個隊列中的函數,並依次執行

// 定義resolve函數
 let resolve = (val) => {
   
   
	 // 1、將狀態變更爲fulfilled, 但是注意一點,Promise是有個特點的,就是狀態只能由pending狀態變更爲fulfilled或者由pending狀態變更爲rejected。且,狀態變化後,不會再變化。故,我們需要先判斷當前是否是等待狀態pending
     if (this.status === 'pending') {
   
     // this指向實例化出來Promise對象
         this.status = 'fulfilled'
         // 2、保存resolve時的值,以便後面調用then()方法時使用
         this.resolveValue = val

         // 執行then的resolve回調
         this.onFullFilledList.forEach(funItem => funItem())
     }
 }
 
 // 定義reject函數
 let reject = (val) => {
   
   
     // 1、狀態變更爲rejected
     if (this.status === 'pending') {
   
   
         this.status = 'rejected'
         // 2、保存reject()時的值
         this.rejectValue = val

         // 執行then的reject回調
         this.onRejectedList.forEach(funItem => funItem())
     }
 }

到此,就不會再有因爲異步代碼而執行不了的問題了。看下完整代碼,並驗證下

class MyPromise {
   
   
    constructor(fun) {
   
   
		// 定義初始狀態(3個狀態分別是pending, fulfilled, rejected)
	    this.status = 'pending'
	    // 定義兩個變量分別來存儲成功時值和失敗時的值
	    this.resolveValue = null
	    this.rejectValue = null
	    this.onFullFilledList = []
	    this.onRejectedList = []

		// 定義resolve函數
	    let resolve = (val) => {
   
   
	        // 1、將狀態變更爲fulfilled, 但是注意一點,Promise是有個特點的,就是狀態只能由pending狀態變更爲fulfilled或者由pending狀態變更爲rejected。且,狀態變化後,不會再變化。故,我們需要先判斷當前是否是等待狀態pending
	        if (this.status === 'pending') {
   
     // this指向實例化出來Promise對象
	            this.status = 'fulfilled'
	            // 2、保存resolve時的值,以便後面調用then()方法時使用
	            this.resolveValue = val
	
	            // 執行then的resolve回調
	            this.onFullFilledList.forEach(funItem => funItem())
	        }
	    }
	    
	    // 定義reject函數
	    let reject = (val) => {
   
   
	        // 1、狀態變更爲rejected
	        if (this.status === 'pending') {
   
   
	            this.status = 'rejected'
	            // 2、保存reject()時的值
	            this.rejectValue = val
	
	            // 執行then的reject回調
	            this.onRejectedList.forEach(funItem => funItem())
	        }
	    }
	
	    try {
   
   
	        fun(resolve, reject)
	    } catch (err) {
   
   
	        reject(err)
	    }

	}
}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
	// 此時,判斷狀態,不同狀態時,分別執行不同的回調
	if (this.status === 'fulfilled') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === 'rejected') {
   
   
		onRejected(this.rejectValue )
    }
    if (this.status === 'pending') {
   
   
        this.onFullFilledList.push(() => {
   
   
            onFullFilled(this.resolveValue)
        })
        this.onRejectedList.push(() => {
   
   
            onRejected(this.rejectValue )
        })
    }
	// 上面的this指向的是調用then的promise實例,故可以直接拿到狀態和返回值
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)  // 123
}, (err) => {
   
   
    console.log(err)
})
輸出了123

Promise.then()的鏈式調用

下面我們來說下then的鏈式調用

promise1.then((res) => {
   
   
	console.log(res)
}, (err) => {
   
   
	console.log(err)
}).then((res) => {
   
   
	console.log(res)
}, (err) => {
   
   
	console.log(err)
})

上一篇文章裏面我就說過,Promise之所以能夠進行鏈式調用,是因爲then()方法內部返回了一個Promise實例,而返回的這個Promise實例在繼續調用了第二個then()方法。並且第二個then的resolve回調的參數,是上一個then的resolve回調函數的返回值。

new Promise((resolve, reject) => {
   
   
	resolve(123)
}).then((res) => {
   
   
	console.log(res)
	return 456
}).then((res) => {
   
   
	console.log(res)
	return 789
}).then((res) => {
   
   
	console.log(res)
})
依次輸出123   456   789

那麼,根據我們所看到的功能,我們來改造下then

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
	// 將then函數內部返回的Promise對象取名爲promise2,後續文檔中將直接以promise2來表示這個對象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
        // 此時,判斷狀態,不同狀態時,分別執行不同的回調
        if (this.status === 'fulfilled') {
   
   
            // 定義一個變量來保存onFullFilled的返回值
            let result = onFullFilled(this.resolveValue)
            resolve(result)
        }
        if (this.status === 'rejected') {
   
   
            // 定義一個變量來保存onRejected的返回值
            let result = onRejected(this.rejectValue )
            reject(result)
        }
        if (this.status === 'pending') {
   
   
            this.onFullFilledList.push(() => {
   
   
                let result = onFullFilled(this.resolveValue)
                resolve(result)
            })
            this.onRejectedList.push(() => {
   
   
                let result = onRejected(this.rejectValue )
                resolve(result)
            })
        }
    })
}

可以看到,我們在調用then時,返回了一個新的Promise實例,並且將這個then(onFullFilled,onRejected)的resolve回調和reject回調的返回值resolve或者reject出去了。
這種方式,對於當onFullFilled返回的是一個普通值來說,是可行的,但如果onFullFilled返回的是一個Promise對象或者函數呢。
從原生Promise的功能上,我們是可以看出的

  1. 當onFullFilled函數返回值是普通值時,下一個then的onFullFilled函數將會以這個返回值作爲參數。
  2. 當onFullFilled函數返回值是一個函數時,下一個then的onFullFilled函數也會直接以這個函數當作參數
  3. 當onFullFilled函數返回值是一個Promise時,then()方法返回的Promise對象(下文統稱爲promise2)的狀態就取決去這個onFullFilled函數所返回的Promise的狀態。promise2 resolve()或者reject()的值,取決於onFullFilled函數返回的Prmise對象resolve()或者reject()的值
    所以,此時,我們是不是需要一個函數來專門判斷這個onFullFilled的返回值到底是普通值還是函數還是Promise對象。並且,當值不同時,處理方式就不一樣。
    如下:

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
	// 將then函數內部返回的Promise對象取名爲promise2,後續文檔中將直接以promise2來表示這個對象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
        // 此時,判斷狀態,不同狀態時,分別執行不同的回調
        if (this.status === 'fulfilled') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定義一個變量來保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0) // 這裏說明下爲說明要用setTimeout, 因爲我這段代碼要用到promise2,而如果是同步代碼,promise2不可在自己的立即執行函數內調用自己
        }
        if (this.status === 'rejected') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定義一個變量來保存onRejected的返回值
            		let result = onRejected(this.rejectValue )
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0)
        }
        if (this.status === 'pending') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定義一個變量來保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定義一個變量來保存onRejected的返回值
	            		let result = onRejected(this.rejectValue )
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
        }
    })
    return promise2
}

下面我們再來寫一下formatPromise()

const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判斷下promise是不是result, 因爲我們知道,我們的result是一個返回值,他可能是一個Promise,那如果他直接返回第一個參數中的promise的話,那麼是會造成死循環的。
	if (promise === result) {
   
   
		return new Error('未知的result')
	}
	// 判斷是否是對象
	if (typeof result === 'object' && result != null) {
   
   
		try {
   
   
			// 如果是對象,先看是否存在then函數
			let then = result.then
			// 如果result.then是一個函數,就說明是Promise對象,或者thenable對象
			if (typeof then === 'function') {
   
   
				// 利用.call將this指向result, 防止result.then()報錯
				then.call(result, res => {
   
   
					resolve(res)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那麼說明只是普通對象,並不是Promise對象,當普通值處理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是對象,那就是普通值或者函數,直接resolve()
		resolve(result)
	}
}

到這一步,我們已經可以處理return一個Promise對象時的情況了。我們來驗證一下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        resolve(234)
    })
}, (err) => {
   
   
    console.log(err)
}).then(res => {
   
   
    console.log(res)
})
// 輸出
123
234

看上圖,返回一個Promise時,也可以正常處理。
但此時,又引出一個問題了,上圖中,return的Promise裏面 ,如果我resolve的不是234,而是resolve了一個新的Promise呢
看下面代碼

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        const promise3 = new MyPromise((resolve, reject) => {
   
   
            resolve(234)
        })
        resolve(promise3)
    })
}, (err) => {
   
   
    console.log(err)
}).then(res => {
   
   
    console.log(res)
})
// 輸出
123
MyPromise {
   
   
  status: 'fulfilled',
  resolveValue: 234,
  rejectValue: null,
  onFullFilledList: [],
  onRejectedList: []
}

可以看出,這個時候,就解析不出234了。更別說如果promise3內部resolve的又是一個Promise了。此時,如果出現這個層層嵌套的。我們是不是要進一步的去進行解析啊,直到解析出一個普通值。那麼這個時候,我們是不是要用到遞歸啊,不斷的利用fomatPromise去解析resolve的值,直到resolve的是一個普通值才停止。下面我們看代碼,繼續完善formatPromise

const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判斷下promise是不是result, 因爲我們知道,我們的result是一個返回值,他可能是一個Promise,那如果他直接返回第一個參數中的promise的話,那麼是會造成死循環的。
	if (promise === result) {
   
   
		return new Error('未知的result')
	}
	// 判斷是否是對象
	if (typeof result === 'object' && result != null) {
   
   
		try {
   
   
			// 如果是對象,先看是否存在then函數
			let then = result.then
			// 如果result.then是一個函數,就說明是Promise對象,或者thenable對象
			if (typeof then === 'function') {
   
   
				// 利用.call將this指向result, 防止result.then()報錯
				then.call(result, res => {
   
   
					// 替換resolve(res)
					formatPromise(promise, res, resolve, reject)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那麼說明只是普通對象,並不是Promise對象,當普通值處理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是對象,那就是普通值或者函數,直接resolve()
		resolve(result)
	}
}

此時,我們再來驗證一下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        const promise3 = new MyPromise((resolve, reject) => {
   
   
            resolve(234)
        })
        resolve(promise3)
    })
}, (err) => {
   
   
    console.log(err)
}).then(res => {
   
   
    console.log(res)
})
// 輸出
123
234

到此,我們then相關的核心代碼就已經完成了。下面我們來看下.catch()

Promise.catch()

上一篇用法中,我們說過,其實.catch(),和.then()的reject回調是一樣的。只是使用位置不一樣罷了。並且.catch()也支持鏈式調用。也就是說.catch和.then其實是一樣的,也返回了一個Promise對象對不對,因爲這樣才能鏈式調用。所以其實catch很簡單,他其實內部就是執行了一個只有reject回調的then函數。下面我們看下代碼

MyPromise.prototype.catch = (err) => {
   
   
    return this.then(undefined, err)
}

下面,我們來驗證一下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        reject(new Error('345'))
    })
}, (err) => {
   
   
    console.log(1)
    console.log(err)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
輸出
123
2
Error: 345

現在,我們這個Promise核心代碼好像是已經完成了是吧! 但其實,這裏還有bug。下面我們繼續看下下面一段代碼

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        reject(new Error('345'))
    })
}, (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log('第二個then')
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
// 輸出
123
2
TypeError: onRejected is not a function

哎,我們發現,當我們在觸發錯誤的地方和catch函數之間插入了一個then的時候,發生了什麼啊,我們發現,catch的回調是執行了,但是這個錯誤並沒有被拋出來,then函數內部報錯了。爲什麼啊。
這個時候我們就要想到一點,Promise的錯誤捕獲是不是一層層捕獲的啊,按理說第一個then內部拋出了錯誤,我們是不是優先在第一個then後面的函數內進行捕獲啊(也就是第二個then內),但是,由於我們並沒有給第二個then定義一個錯誤捕獲的函數,所以這個時候是不是就報錯了啊 , 說onRejected(也就是then的第二個參數)不是一個函數。但是原生Promise功能是怎樣的啊。當沒有第二個參數的時候,錯誤是不是會繼續往下傳遞啊。所以這個時候,我們需要判斷一下第二個參數到底有沒有。如果沒有,或者不是函數,我們是不是要將錯誤,繼續往下拋出啊。下面我們改造下then,繼續看代碼

const isFun = (fun) => typeof fun === 'function'
MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判斷onRejected是否存在或者是否是函數,如果不是函數或者不存在,我們讓它等於一個函數,並且在函數內繼續將err向下拋出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }

	// 將then函數內部返回的Promise對象取名爲promise2,後續文檔中將直接以promise2來表示這個對象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
        // 此時,判斷狀態,不同狀態時,分別執行不同的回調
        if (this.status === 'fulfilled') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定義一個變量來保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0) // 這裏說明下爲說明要用setTimeout, 因爲我這段代碼要用到promise2,而如果是同步代碼,promise2不可在自己的立即執行函數內調用自己
        }
        if (this.status === 'rejected') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定義一個變量來保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0)
        }
        if (this.status === 'pending') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定義一個變量來保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定義一個變量來保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
        }
    })
    return promise2
}

此時,我們再驗證下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        reject(new Error('345'))
    })
}, (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log('第二個then')
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
// 輸出
123
2
Error: 345

好,catch的錯誤一步一步向下傳遞的問題我們解決了。那麼then是不是也有這樣的問題啊,then函數的值一步一步向下傳遞的問題我們是不是還沒解決?大家還記得我們上篇文章中提到的值穿透的現象嗎?當我們的then函數的第一個參數不存在,或者不是函數時,他的值是不是會穿透到第二個then的resolve回調中啊。怎麼實現呢,原理和catch其實是一樣的。話不多說,直接看代碼

const isFun = (fun) => typeof fun === 'function'
MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判斷onRejected是否存在或者是否是函數,如果不是函數或者不存在,我們讓它等於一個函數,並且在函數內繼續將err向下拋出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }
    onFullFilled = isFun(onFullFilled)? onFullFilled : res => res

	// 將then函數內部返回的Promise對象取名爲promise2,後續文檔中將直接以promise2來表示這個對象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
        // 此時,判斷狀態,不同狀態時,分別執行不同的回調
        if (this.status === 'fulfilled') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定義一個變量來保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0) // 這裏說明下爲說明要用setTimeout, 因爲我這段代碼要用到promise2,而如果是同步代碼,promise2不可在自己的立即執行函數內調用自己
        }
        if (this.status === 'rejected') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定義一個變量來保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0)
        }
        if (this.status === 'pending') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定義一個變量來保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定義一個變量來保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
        }
    })
    return promise2
}

我們再來驗證下值穿透的現象

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then('aaa', (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log('第二個then')
    console.log(res)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
// 輸出
第二個then
123

好了,這個時候我們的代碼功能就已經完整了。下面附上全部代碼

const isFun = (fun) => typeof fun === 'function'
const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判斷下promise是不是result, 因爲我們知道,我們的result是一個返回值,他可能是一個Promise,那如果他直接返回第一個參數中的promise的話,那麼是會造成死循環的。
	if (promise === result) {
   
   
		return new Error('未知的result')
	}
	// 判斷是否是對象
	if (typeof result === 'object' && result != null) {
   
   
		try {
   
   
			// 如果是對象,先看是否存在then函數
			let then = result.then
			// 如果result.then是一個函數,就說明是Promise對象,或者thenable對象
			if (typeof then === 'function') {
   
   
				// 利用.call將this指向result, 防止result.then()報錯
				then.call(result, res => {
   
   
					formatPromise(promise, res, resolve, reject)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那麼說明只是普通對象,並不是Promise對象,當普通值處理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是對象,那就是普通值或者函數,直接resolve()
		resolve(result)
	}
}

class MyPromise {
   
   
    constructor (fun) {
   
   
        // 定義初始狀態(3個狀態分別是pending, fulfilled, rejected)
        this.status = 'pending'
        // 定義兩個變量分別來存儲成功時值和失敗時的值
        this.resolveValue = null
        this.rejectValue = null
        this.onFullFilledList = []
        this.onRejectedList = []

        // 定義resolve函數
        let resolve = (val) => {
   
   
            // 1、將狀態變更爲fulfilled, 但是注意一點,Promise是有個特點的,就是狀態只能由pending狀態變更爲fulfilled或者由pending狀態變更爲rejected。且,狀態變化後,不會再變化。故,我們需要先判斷當前是否是等待狀態pending
            if (this.status === 'pending') {
   
     // this指向實例化出來Promise對象
                this.status = 'fulfilled'
                // 2、保存resolve時的值,以便後面調用then()方法時使用
                this.resolveValue = val

                // 執行then的resolve回調
                this.onFullFilledList.forEach(funItem => funItem())
            }
        }
        
        // 定義reject函數
        let reject = (val) => {
   
   
            // 1、狀態變更爲rejected
            if (this.status === 'pending') {
   
   
                this.status = 'rejected'
                // 2、保存reject()時的值
                this.rejectValue = val

                // 執行then的reject回調
                this.onRejectedList.forEach(funItem => funItem())
            }
        }

        try {
   
   
            fun(resolve, reject)
        } catch (err) {
   
   
            reject(err)
        }
    }

}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判斷onRejected是否存在或者是否是函數,如果不是函數或者不存在,我們讓它等於一個函數,並且在函數內繼續將err向下拋出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }
    onFullFilled = isFun(onFullFilled) ? onFullFilled : res => res

	// 將then函數內部返回的Promise對象取名爲promise2,後續文檔中將直接以promise2來表示這個對象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
        // 此時,判斷狀態,不同狀態時,分別執行不同的回調
        if (this.status === 'fulfilled') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定義一個變量來保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0) // 這裏說明下爲說明要用setTimeout, 因爲我這段代碼要用到promise2,而如果是同步代碼,promise2不可在自己的立即執行函數內調用自己
        }
        if (this.status === 'rejected') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定義一個變量來保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0)
        }
        if (this.status === 'pending') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定義一個變量來保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定義一個變量來保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
        }
    })
    return promise2
}

MyPromise.prototype.catch = function (err) {
   
   
    return this.then(undefined, err)
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then('aaa', (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log('第二個then')
    console.log(res)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})

核心代碼我們已經實現了,剩下的幾個Promise.Resolve(),Promise.Reject(),Promise.all(),Promise.race()就比較簡單了。我們就不做過多的說明了。大家直接看代碼吧

Promise.resolve()

const isPromise = (value) => {
   
   
    if ((value != null && typeof value === 'object') || typeof value === 'function') {
   
   
        if (typeof value.then == 'function') {
   
   
            return true
        }
    } else {
   
   
        return false
    }
}
MyPromise.resolve = (value) => {
   
   
    // 如果是一個promise對象就直接將這個對象返回
    if (isPromise(value)) {
   
   
        return value
    } else {
   
   
        // 如果是一個普通值就將這個值包裝成一個promise對象之後返回
        return new MyPromise((resolve, reject) => {
   
   
            resolve(value)
        })
    }
}

Promise.reject()

MyPromise.reject = (value) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        reject(value)
    })
}

Promise.all()

all的特點就是如果有其中一個返回了錯誤(reject),那麼就立即返回錯誤。否則,必須等到所有的都成功之後纔會返回

MyPromise.all = (arr) => {
   
   
    // 返回一個promise
    return new MyPromise((resolve, reject) => {
   
   
        let resArr = [] // 存儲處理的結果的數組
        // 判斷每一項是否處理完了
        let index = 0
        function processData(i, data) {
   
   
            resArr[i] = data
            index += 1
            if (index == arr.length) {
   
   
                // 處理異步,要使用計數器,不能使用resArr==arr.length
                resolve(resArr)
            }
        }
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    processData(i, data)
                }, (err) => {
   
   
                    reject(err) // 只要有一個傳入的promise沒執行成功就走reject
                    return
                })
            } else {
   
   
                processData(i, arr[i])
            }
        }
    })
}

Promise.race()

race的特點是,哪個先返回狀態,就立即返回這個的狀態和值(和賽跑一樣,哪個先到,我就用哪個)

MyPromise.race = (arr) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    resolve(data)// 哪個先完成就返回哪一個的結果
                    return
                }, (err) => {
   
   
                    reject(err)
                    return
                })
            } else {
   
   
                resolve(arr[i])
            }
        }
    })
}

好了,代碼都整完了。有不懂的,可以私信我或者直接評論中問。最後再附上一分最全的代碼,感謝閱覽!

// 是否是函數
const isFun = (fun) => typeof fun === 'function'

// 是否是Promise對象
const isPromise = (value) => {
   
   
    if ((value != null && typeof value === 'object') || typeof value === 'function') {
   
   
        if (typeof value.then == 'function') {
   
   
            return true
        }
    } else {
   
   
        return false
    }
}
// then函數中,返回值的處理函數,判斷返回值的類型,並做處理
const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判斷下promise是不是result, 因爲我們知道,我們的result是一個返回值,他可能是一個Promise,那如果他直接返回第一個參數中的promise的話,那麼是會造成死循環的。
	if (promise === result) {
   
   
		return new Error('未知的result')
	}
	// 判斷是否是對象
	if (typeof result === 'object' && result != null) {
   
   
		try {
   
   
			// 如果是對象,先看是否存在then函數
			let then = result.then
			// 如果result.then是一個函數,就說明是Promise對象,或者thenable對象
			if (typeof then === 'function') {
   
   
				// 利用.call將this指向result, 防止result.then()報錯
				then.call(result, res => {
   
   
					formatPromise(promise, res, resolve, reject)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那麼說明只是普通對象,並不是Promise對象,當普通值處理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是對象,那就是普通值或者函數,直接resolve()
		resolve(result)
	}
}

class MyPromise {
   
   
    constructor (fun) {
   
   
        // 定義初始狀態(3個狀態分別是pending, fulfilled, rejected)
        this.status = 'pending'
        // 定義兩個變量分別來存儲成功時值和失敗時的值
        this.resolveValue = null
        this.rejectValue = null
        this.onFullFilledList = []
        this.onRejectedList = []

        // 定義resolve函數
        let resolve = (val) => {
   
   
            // 1、將狀態變更爲fulfilled, 但是注意一點,Promise是有個特點的,就是狀態只能由pending狀態變更爲fulfilled或者由pending狀態變更爲rejected。且,狀態變化後,不會再變化。故,我們需要先判斷當前是否是等待狀態pending
            if (this.status === 'pending') {
   
     // this指向實例化出來Promise對象
                this.status = 'fulfilled'
                // 2、保存resolve時的值,以便後面調用then()方法時使用
                this.resolveValue = val

                // 執行then的resolve回調
                this.onFullFilledList.forEach(funItem => funItem())
            }
        }
        
        // 定義reject函數
        let reject = (val) => {
   
   
            // 1、狀態變更爲rejected
            if (this.status === 'pending') {
   
   
                this.status = 'rejected'
                // 2、保存reject()時的值
                this.rejectValue = val

                // 執行then的reject回調
                this.onRejectedList.forEach(funItem => funItem())
            }
        }

        try {
   
   
            fun(resolve, reject)
        } catch (err) {
   
   
            reject(err)
        }
    }

}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判斷onRejected是否存在或者是否是函數,如果不是函數或者不存在,我們讓它等於一個函數,並且在函數內繼續將err向下拋出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }
    onFullFilled = isFun(onFullFilled) ? onFullFilled : res => res

	// 將then函數內部返回的Promise對象取名爲promise2,後續文檔中將直接以promise2來表示這個對象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分別resolve()時的回調函數和reject()時的回調函數
        // 此時,判斷狀態,不同狀態時,分別執行不同的回調
        if (this.status === 'fulfilled') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定義一個變量來保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0) // 這裏說明下爲說明要用setTimeout, 因爲我這段代碼要用到promise2,而如果是同步代碼,promise2不可在自己的立即執行函數內調用自己
        }
        if (this.status === 'rejected') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定義一個變量來保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代碼執行的錯誤
				}
			}, 0)
        }
        if (this.status === 'pending') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定義一個變量來保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定義一個變量來保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代碼執行的錯誤
					}
				}, 0)
            })
        }
    })
    return promise2
}

MyPromise.prototype.catch = function (err) {
   
   
    return this.then(undefined, err)
}

MyPromise.resolve = (value) => {
   
   
    // 如果是一個promise對象就直接將這個對象返回
    if (isPromise(value)) {
   
   
        return value
    } else {
   
   
        // 如果是一個普通值就將這個值包裝成一個promise對象之後返回
        return new MyPromise((resolve, reject) => {
   
   
            resolve(value)
        })
    }
}

MyPromise.reject = (value) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        reject(value)
    })
}

MyPromise.all = (arr) => {
   
   
    // 返回一個promise
    return new MyPromise((resolve, reject) => {
   
   
        let resArr = [] // 存儲處理的結果的數組
        // 判斷每一項是否處理完了
        let index = 0
        function processData(i, data) {
   
   
            resArr[i] = data
            index += 1
            if (index == arr.length) {
   
   
                // 處理異步,要使用計數器,不能使用resArr==arr.length
                resolve(resArr)
            }
        }
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    processData(i, data)
                }, (err) => {
   
   
                    reject(err) // 只要有一個傳入的promise沒執行成功就走reject
                    return
                })
            } else {
   
   
                processData(i, arr[i])
            }
        }
    })
}

MyPromise.race = (arr) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    resolve(data)// 哪個先完成就返回哪一個的結果
                    return
                }, (err) => {
   
   
                    reject(err)
                    return
                })
            } else {
   
   
                resolve(arr[i])
            }
        }
    })
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then('aaa', (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log('第二個then')
    console.log(res)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章