注: 本文中寫的類只是為了了解Promise類的內部原理而模擬出來一個, 并不一定符合類似的規范或者效率多么高, 但是基本的功能還是實作了的.
注: 本文代碼運行環境: NodeJS v14.9.0
用法
如下, 這是一個傳統的使用回呼函式的異步代碼
function getAnInt(callback) {
setTimeout(() => {
callback(81)
}, 500)
}
function sqrt(n, resolve, reject) {
setTimeout(() => {
let res = Math.sqrt(n)
if (parseInt(res) === res) {
resolve(res)
} else {
reject("cannot get an int")
}
}, 500)
}
let errHandler = err => console.log("Error " + err)
getAnInt(v1 => {
console.log(v1)
sqrt(v1, v2 => {
console.log(v2)
sqrt(v2, v3 => {
console.log(v3)
sqrt(v3, v4 => {
console.log(v4)
}, errHandler)
}, errHandler)
}, errHandler)
})
執行結果:
81
9
3
Error cannot get an int
有沒有感覺眼花繚亂? 這金字塔狀的代碼被親切地稱為回呼地獄, 下面就是我們的主角Promise上場的時候了, 醬醬醬醬
function getAnInt() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(81)
}, 500)
})
}
function sqrt(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = Math.sqrt(n)
if (parseInt(res) === res) {
resolve(res)
} else {
reject("cannot get an int")
}
}, 500)
})
}
getAnInt().then(v1 => {
console.log(v1)
return sqrt(v1)
}).then(v2 => {
console.log(v2)
return sqrt(v2)
}).then(v3 => {
console.log(v3)
return sqrt(v3)
}).then(v4 => {
console.log(v4)
}).catch(err => {
console.log("Error " + err)
})
執行結果:
81
9
3
Error cannot get an int
結果一模一樣, 但是這個代碼寫出來的感覺, 就是要清晰了好多好多好多好多好多好多好多好多好多好多好多好多
介紹
在Promise/A+標準中定義了Promise到底是個什么東西, 這里挑出重點部分, 其余的規范如果想看的話點這里去官網
promise含有then方法, 沒有規定其它的方法.then方法會回傳一個新的promisethen方法的引數是onFulfilled, onRejected, 它們都是可選的(當然都是函式型別)promise有三個狀態,pending(代辦),fulfilled(完成)和rejected(被拒絕), 狀態只能從pending轉成另外兩個, 然后就不能再轉了.- 如果
onRejected或者onFulfilled回傳了一個Promise物件, 需要得出它的結果再傳給下一個then方法里對應的地方
因為本文代碼中有很多的 resolve, 所以這里的代碼使用resolved(被解決)代替fulfilled
為什么沒有列出來更多的內容呢, 因為其它的內容大多和兼容性有關, 與這個實作原理關系不是太大, 還有的是到具體實作函式的時候才會用到的規范, 所以我沒有列出來
注: catch方法是ES6標準里的, 它的原理是then(null, onRejected)
實作
注: 本文代碼不考慮throw, 為了只體現原理, 讓代碼盡可能更簡單.
建構式
Promise的建構式通常傳入一個執行者函式, 這個函式里面可能是異步邏輯(這么說的意思就是也可能不是), 接受兩個引數: resolve和reject.
-
呼叫
resolve(value)就代表方法成功執行了,Promise會把resolve中傳入的value傳給then方法里的引數 -
呼叫
reject(reason)就是執行出錯了,Promise會把reject中傳入的reason傳給then方法里的引數
好, 下面開始做點準備作業
const Pending = 'pending'
const Resolved = 'resolved'
const Rejected = 'rejected'
class MyPromise {}
誒, 這段代碼我感覺不用解釋了吧? 下面的我會在注釋或者是代碼塊下方說明
class MyPromise {
constructor(executor) {
// 狀態
this.status = Pending
// 正常運行回傳的結果
this.value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/null
// 發生錯誤的原因
this.reason = null
// 詳見這段代碼塊下面寫的 注1
this.onRejected = () => {}
this.onResolved = () => {}
let resolve = value => {
// 如果不是Pending就忽略
if (this.status !== Pending) {
return
}
this.status = Resolved
this.value = value
this.onResolved(value)
}
let reject = reason => {
// 如果不是Pending就忽略
if (this.status !== Pending) {
return
}
this.status = Rejected
this.reason = reason
this.onRejected(reason)
}
// 見 注2
executor(resolve, reject)
}
}
-
注1: 這是兩個被
reject或者resolve后呼叫的回呼函式, 我看的別人實作的版本大多是一個陣列, 然后呼叫的時候一個接一個呼叫里面的函式.我認為對同一個promise呼叫多次
then方法的時候很少, 而且本文只是一個思路展示, 并不嚴格遵守A+規范, 所以這里就直接寫了個什么也沒干的函式在這里也分析一下, 在
then方法呼叫的時候, 如果呼叫then時的狀態是Pending, 那么就設定一下當前物件里的onRejected和onResolved, 具體設定什么在后面的代碼里會提到; 如果狀態不是Pending, 就代表這兩個函式早就執行完了, 就需要根據this.value和this.reason具體的呼叫then函式中傳進來的onRejected和onResolved. -
注2: 這里直接同步呼叫了, 沒有異步呼叫. 因為如果這個操作真的需要異步的話, 在
executor函式里面就會有異步方法了(如setTimeout), 不需要Promise類給它辦.
Then方法
然后就是then方法啦~
注意: then方法要求每次回傳新的Promise物件.
先寫個框架
then(onResolved, onRejected) {
let funcOrNull = f => typeof f === "function" ? f : null
onResolved = funcOrNull(onResolved)
onRejected = funcOrNull(onRejected)
if (this.status === Rejected) {
return new MyPromise((resolve, reject) => {
})
} else if (this.status === Resolved) {
return new MyPromise((resolve, reject) => {
})
} else {
return new MyPromise((resolve, reject) => {
})
}
}
Rejected
如果是狀態是rejected, 那么
if (this.status === Rejected) {
return new MyPromise((resolve, reject) => {
let value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/(onRejected === null ? reject : onRejected)(this.reason)
if (value instanceof MyPromise) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
}
這些實作的代碼包括下面的elseif和else塊就是最難理解的了, 我當時是好久好久也沒有理解, 接下來我會就像數學里面一樣分類討論:
關于Rejected塊的詳細說明(盡管也就10行)
理解了Rejected塊, 那么Resolved塊和他幾乎一模一樣, 只是函式名字不一樣而已, 所以我這里會分析的盡可能詳細
-
如果呼叫的時候是這樣的:
new MyPromise((resolve, reject) => { reject("I rejected the promise") }).then(null, console.log)先分析構造方法, 創建
Promise物件的時候, 這里它的狀態就變成Rejected, 但是其他的什么事都沒干, 讓我們來看前面的代碼this.onRejected = () => {} this.onResolved = () => {} let reject = reason => { if (this.status !== Pending) { return } this.status = Rejected this.reason = reason this.onRejected(reason) } executor(resolve, reject)這個時候
this.onRejected還是個空函式, 所以呼叫它也沒什么用
接下來到then方法了, 讓我們來看上面if塊里的代碼return new MyPromise((resolve, reject) => { let value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/(onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } })可以看出它執行了
let value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/onRejected(reason), 然后呼叫resolve(value), 之后這個新的Promise狀態就是Resolved了.至于為什么這里要用
resolve, 我是通過NodeJS做了個實驗看看NodeJS對這件事是怎么干的, 代碼如下let p1 = new Promise((resolve, reject) => { reject("I rejected the promise") }) let p2 = p1.then(null, reason => { return 'I am from onRejected function' }) // 這里是為了不管到底是什么狀態都能把p1和p2輸出出來 p2.then(() => console.log(p1, p2), () => console.log(p1, p2))輸出(原本的執行結果沒有換行, 我為了方便看自己加上的)
Promise { <rejected> 'I rejected the promise' } Promise { 'I am from onRejected function' }這就看出來NodeJS是在處理完錯誤之后把
onRejected的回傳值用resolve函式處理了
-
如果呼叫的時候是這樣的
new MyPromise((resolve, reject) => { reject("I just rejected the promise") }).then(null, null).then(null, console.log)這個時候就要考慮不能把錯誤資訊丟掉了, 為了實作這個"穿透"功能, 我們可以研究一下NodeJS是怎么干的
let p1 = new Promise((resolve, reject) => { reject("I rejected the promise") }).then(null, null) p1.then(() => console.log(p1), () => console.log(p1))輸出
Promise { <rejected> 'I rejected the promise' }這就很簡單了, NodeJS是把新的
Promise物件繼續呼叫reject并且傳遞錯誤資訊. 所以再看上面if塊里的代碼return new MyPromise((resolve, reject) => { let value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/(onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } })可以看出這里也是在
onRejected空的時候直接用reject方法把新的Promise物件的狀態設定成了Rejected并且也把this.reason錯誤資訊傳了過去.
展現成代碼的話, 就是執行了reject(this.reason)你可能會疑惑, 那么
reject(this.reason)回傳值應該是undefined, 然后又呼叫了resolve(value)是怎么回事呢?
這里我們要看前面的代碼let resolve = value =https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/> { // 如果不是Pending就忽略 if (this.status !== Pending) { return } this.status = Resolved this.value = value this.onResolved(value) }在呼叫完
reject之后, 這里的status就變成了Rejected, 這個方法就不會呼叫了呀
你可能還會疑惑, 這里的代碼
if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) }雖然說你知道回傳值是
Promise要得出結果, 但這是onRejected回傳的值, 為什么第二行要這么寫?還是老方法, 我們看看NodeJS這個地方怎么實作的
let p = new Promise((resolve, reject) => { reject("I rejected the promise") }).then(null, reason => { return new Promise((resolve, reject) => { resolve("Hello~") }) }).then(value =https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/> { console.log("Value " + value) }, reason => { console.log("Reason " + reason) })運行結果
Value Hello~所以說, 這里需要這么寫, 讓這個
then里回傳的Promise物件then方法的onResolved方法直接呼叫新物件的resolve和reject方法來操作這個新物件
Resolved
如果上面的都能理解了, 那么下面這個elseif塊就特別好理解了
else if (this.status === Resolved) {
return new MyPromise((resolve, reject) => {
let value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/(onResolved === null ? resolve : onResolved)(this.value)
if (value instanceof MyPromise) {
value.then(resolve, reject)
} else {
resolve(value)
}
}
}
Pending
在else塊里, 也就是狀態是Pending的時候, 需要做的事情幾乎和上面的if和elseif塊一樣
在Promise物件狀態是Pending的時候, 不能通過this.value和this.reason獲取值, 但是, 我們可以通過設定this.onRejected和this.onResolved這兩個函式, 因為當Promise的executor執行完的時候一定會呼叫這兩個函式中的一個, 并且呼叫它們的時候都會帶上value和reason, 所以這里的代碼需要這么寫
else {
return new MyPromise((resolve, reject) => {
this.onResolved = value =https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/> {
let v = (onResolved === null ? resolve : onResolved)(value)
if (v instanceof MyPromise) {
v.then(resolve, reject)
} else {
resolve(v)
}
}
this.onRejected = reason => {
let v = (onRejected === null ? reject : onRejected)(reason)
if (v instanceof MyPromise) {
v.then(resolve, reject)
} else {
resolve(v)
}
}
})
}
最后加上一個catch方法, 其實就是一個語法糖, 既然ES6都加上了, 那我也加上吧
catch(onRejected) {
return this.then(null, onRejected)
}
最后的測驗
嘿咻, 終于弄完了, 接下來就是實驗新物件的時候啦!(這么說好像有點怪怪的呢)
還是文章開頭那熟悉的味道
function getAnInt() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(81)
}, 500)
})
}
function sqrt(n) {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
let res = Math.sqrt(n)
if (parseInt(res) === res) {
resolve(res)
} else {
reject("cannot get an int")
}
}, 500)
})
}
getAnInt().then(v1 => {
console.log(v1)
return sqrt(v1)
}).then(v2 => {
console.log(v2)
return sqrt(v2)
}).then(v3 => {
console.log(v3)
return sqrt(v3)
}).then(v4 => {
console.log(v4)
}).catch(err => {
console.log("Error " + err)
})
結果
81
9
3
Error cannot get an int
附: 全代碼
const Pending = 'pending'
const Resolved = 'resolved'
const Rejected = 'rejected'
class MyPromise {
constructor(executor) {
// 狀態
this.status = Pending
// 正常運行回傳的結果
this.value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/null
// 發生錯誤的原因
this.reason = null
// 見 注1
this.onRejected = () => {}
this.onResolved = () => {}
let resolve = value => {
// 如果不是Pending就忽略
if (this.status !== Pending) {
return
}
this.status = Resolved
this.value = value
this.onResolved(value)
}
let reject = reason => {
// 如果不是Pending就忽略
if (this.status !== Pending) {
return
}
this.status = Rejected
this.reason = reason
this.onRejected(reason)
}
// 見 注2
executor(resolve, reject)
}
then(onResolved, onRejected) {
let funcOrNull = f => typeof f ==="function" ? f : null
onResolved = funcOrNull(onResolved)
onRejected = funcOrNull(onRejected)
if (this.status === Rejected) {
return new MyPromise((resolve, reject) => {
let value = https://www.cnblogs.com/nekodaisuki/archive/2021/02/14/(onRejected === null ? reject : onRejected)(this.reason)
if (value instanceof MyPromise) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
} else if (this.status === Resolved) {
return new MyPromise((resolve, reject) => {
let value = (onResolved === null ? resolve : onResolved)(this.value)
if (value instanceof MyPromise) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
} else {
return new MyPromise((resolve, reject) => {
this.onResolved = value => {
let v = (onResolved === null ? resolve : onResolved)(value)
if (v instanceof MyPromise) {
v.then(resolve, reject)
} else {
resolve(v)
}
}
this.onRejected = reason => {
let v = (onRejected === null ? reject : onRejected)(reason)
if (v instanceof MyPromise) {
v.then(resolve, reject)
} else {
resolve(v)
}
}
})
}
}
catch(onRejected) {
return this.then(null, onRejected)
}
}
// 測驗模塊!
function getAnInt() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(81)
}, 500)
})
}
function sqrt(n) {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
let res = Math.sqrt(n)
if (parseInt(res) === res) {
resolve(res)
} else {
reject("cannot get an int")
}
}, 500)
})
}
getAnInt().then(v1 => {
console.log(v1)
return sqrt(v1)
}).then(v2 => {
console.log(v2)
return sqrt(v2)
}).then(v3 => {
console.log(v3)
return sqrt(v3)
}).then(v4 => {
console.log(v4)
}).catch(err => {
console.log("Error " + err)
})
參考: https://zhuanlan.zhihu.com/p/21834559
https://zhuanlan.zhihu.com/p/183801144
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/259379.html
標籤:其他
上一篇:JS Promise的用法, 以及自己模擬一個Promise
下一篇:JavaScript物件
