上一篇,我們說了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的特點
- 首先,毫無疑問,Promise()是一個構造函數, 並且,存在3種狀態,pending, fulfilled(也可以叫Resolved), rejected。分別表示等待時,成功時,失敗時
- Promise實例化時,傳了個參數,並且這個參數是個函數(並且是個立即執行函數),同時這個函數還有兩個參數,且這兩個參數,依然是函數,分別是resolve(), reject()
- then()函數中的第一個參數(後文我們統稱爲then的resolve回調),是在調用then()方法的Promise對象的狀態變爲fulfilled時被執行的,而第二個參數(後文我們統稱爲rejected回調),是在Promise對象的狀態變成rejected時被調用的。
- 通過第四代,我們還可以看出,在resolve()函數執行時,是將Promise對象的狀態變更成了fulfilled,從而觸發了then的resolve回調函數的執行。而reject()函數執行時,是將Promise對象的狀態變更成了rejected,從而觸發了then的reject回調的執行
- resolve(res)時的值,就是then的resolve回調的參數,reject(err)的值就是thenreject回調的參數
- 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基礎雛形
- 知曉了他是個構造函數,那我們就創建個構造函數,並定義好狀態,並立即執行實例化時傳入的函數
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出來的值。
- 在下一步,狀態已經發生改變了,我們是不是要觸發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的功能上,我們是可以看出的
- 當onFullFilled函數返回值是普通值時,下一個then的onFullFilled函數將會以這個返回值作爲參數。
- 當onFullFilled函數返回值是一個函數時,下一個then的onFullFilled函數也會直接以這個函數當作參數
- 當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)
})