上一篇,我們說了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)
})
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/229216.html
標籤:其他
下一篇:單點登陸的三種實作方式
