目錄
- 手寫 Promise
- Promise 建構式
- 三個狀態(pending、rejected和fulfilled)
- 三個方法(resolve、reject和then)
resolve方法實作要點reject方法實作要點then方法實作要點
- Promise 解決程式(resolvePromise方法)
- 運行官方測驗用例
- Promise 其他方法補充
- 容錯處理方法
- 靜態方法
手寫 Promise
Promise 建構式
我們先來寫 Promise 建構式的屬性和值,以及處理new Promise()時會傳入的兩個回呼函式,如下:
class myPromise {
constructor(func) {
this.state = 'pending' // Promise狀態
this.value = https://www.cnblogs.com/chscript/p/undefined // 成功的值
this.reason = undefined // 錯誤的值
this.resolveCallbacks = [] // 收集解決回呼函式
this.rejectCallbacks = [] // 收集錯誤回呼函式
try { // 對傳入的函式進行try...catch...做容錯處理
func(this.resolve, this.reject) // 執行傳入的兩個回呼函式
} catch (e) {
this.reject(e)
}
}
}
三個狀態(pending、rejected和fulfilled)
pending:待定狀態,待定 Promise ,只有在then方法執行后才會保持此狀態,
rejected:拒絕狀態,終止 Promise ,只有在reject方法執行后才會由 pending 更改為此狀態,
fulfilled:解決狀態,終止 Promise ,只有在resolve方法執行后才會由 pending 更改為此狀態,
注意:其中只有 pedding 狀態可以變更為 rejected 或 fulfilled ,rejected 或 fulfilled 不能更改其他任何狀態,
三個方法(resolve、reject和then)
resolve方法實作要點
- 狀態由
pending為fulfilled, resolve方法傳入的value引數賦值給this.value- 按順序執行
resolveCallbacks里面所有解決回呼函式 - 利用
call方法將解決回呼函式內部的 this 系結為undefined
坑點 1:resolve方法內部 this 指向會丟失,進而造成this.value丟失,
解決辦法:我們將resolve方法定義為箭頭函式,在建構式執行后,箭頭函式可以系結實體物件的 this 指向,
// 2.1. Promise 狀態
resolve = (value) => { // 在執行建構式時內部的this通過箭頭函式系結實體物件
if (this.state === 'pending') {
this.state = 'fulfilled' // 第一點
this.value = https://www.cnblogs.com/chscript/p/value // 第二點
while (this.resolveCallbacks.length > 0) { // 第三點
this.resolveCallbacks.shift().call(undefined) // 第四點
}
}
}
reject方法實作要點
- 狀態由
pending為rejected reject方法傳入的reason引數賦值給this.reason- 按順序執行
rejectCallbacks里面所有拒絕回呼函式 - 利用
call方法將拒絕回呼函式內部的 this 系結為undefined
坑點 1: reject 方法內部 this 指向會丟失,進而造成this.reason丟失,
解決辦法:我們將reject方法定義為箭頭函式,在建構式執行后,箭頭函式可以系結實體物件的 this 指向,
// 2.1. Promise 狀態
reject = (reason) => { // 在執行建構式時內部的this通過箭頭函式系結實體物件
if (this.state === 'pending') {
this.state = 'rejected' // 第一點
this.reason = reason // 第二點
while (this.rejectCallbacks.length > 0) { // 第三點
this.rejectCallbacks.shift().call(undefined) // 第四點
}
}
}
then方法實作要點
-
判斷then方法的兩個引數
onRejected和onFulfilled是否為function,1.1
onRejected和onFulfilled都是function,繼續執行下一步,1.2
onRejected不是function,將onRejected賦值為箭頭函式,引數為reason執行throw reason1.3
onFulfilled不是function,將onFulfilled賦值為箭頭函式,引數為value執行return value -
當前Promise狀態為rejected:
2.1
onRejected方法傳入this.reason引數,異步執行,2.2 對執行的
onRejected方法做容錯處理,catch錯誤作為reject方法引數執行, -
當前Promise狀態為fulfilled:
3.1
onFulfilled方法傳入this.value引數,異步執行,3.2 對執行的
onFulfilled方法做容錯處理,catch錯誤作為reject方法引數執行, -
當前Promise狀態為pending:
4.1 收集
onFulfilled和onRejected兩個回呼函式分別push給resolveCallbacks和rejectCallbacks,4.2 收集的回呼函式同樣如上所述,先做異步執行再做容錯處理,
-
回傳一個 Promise 實體物件,
// 2.2. then 方法
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value =https://www.cnblogs.com/chscript/p/> value // 第一點
onRejected = typeof onRejected ==='function' ? onRejected : reason => { throw reason } // 第一點
const p2 = new myPromise((resolve, reject) => {
if (this.state === 'rejected') { // 第二點
queueMicrotask(() => {
try {
onRejected(this.reason)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'fulfilled') { // 第三點
queueMicrotask(() => {
try {
onFulfilled(this.value)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'pending') { // 第四點
this.resolveCallbacks.push(() => {
queueMicrotask(() => {
try {
onFulfilled(this.value)
} catch (e) {
reject(e)
}
})
})
this.rejectCallbacks.push(() => {
queueMicrotask(() => {
try {
onRejected(this.reason)
} catch (e) {
reject(e)
}
})
})
}
})
return p2 // 第五點
}
Promise 解決程式(resolvePromise方法)
旁白:其實這個解決程式才是實作核心Promise最難的一部分,因為Promise A+規范對于這部分說的比較繞,
我們直擊其實作要點,能跑通所有官方用例就行,如下:
-
如果x和promise參考同一個物件:
1.1 呼叫
reject方法,其引數為new TypeError() -
如果x是一個promise或x是一個物件或函式:
2.1 定義一個
called變數用于記錄then.call引數中兩個回呼函式的呼叫情況,2.2 定義一個
then變數等于x.then2.3
then是一個函式,使用call方法系結x物件,傳入解決回呼函式和拒絕回呼函式作為引數,同時利用called變數記錄then.call引數中兩個回呼函式的呼叫情況,2.4
then不是函式,呼叫resolve方法解決Promise,其引數為x2.5 對以上 2.2 檢索屬性和 2.3 呼叫方法的操作放在一起做容錯處理,
catch錯誤作為reject方法引數執行,同樣利用called變數記錄then.call引數中兩個回呼函式的呼叫情況, -
如果x都沒有出現以上兩種狀況:
呼叫
resolve方法解決Promise,其引數為x
// 2.3 Promise解決程式
function resolvePromise(p2, x, resolve, reject) {
if (x === p2) {
// 2.3.1 如果promise和x參考同一個物件
reject(new TypeError())
} else if ((x !== null && typeof x === 'object') || typeof x === 'function') {
// 2.3.2 如果x是一個promise
// 2.3.3 如果x是一個物件或函式
let called
try {
let then = x.then // 檢索x.then屬性,做容錯處理
if (typeof then === 'function') {
then.call(x, // 使用call系結會立即執行then方法,做容錯處理
(y) => { // y也可能是一個Promise,遞回呼叫直到y被resolve或reject
if (called) { return }
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) => {
if (called) { return }
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) { return }
called = true
reject(e)
}
} else {
resolve(x)
}
}
called變數的作用:記錄then.call傳入引數(兩個回呼函式)的呼叫情況,
根據Promise A+ 2.3.3.3.3規范:兩個引數作為函式第一次呼叫優先,以后的呼叫都會被忽略,
因此我們在以上兩個回呼函式中這樣處理:
- 已經呼叫過一次:此時
called已經為true,直接return忽略 - 首次呼叫:此時
called為undefined,呼叫后called設為true
注意:2.3 中的catch可能會發生(兩個回呼函式)已經呼叫但出現錯誤的情況,因此同樣按上述說明處理,
運行官方測驗用例
在完成上面的代碼后,我們最終整合如下:
class myPromise {
constructor(func) {
this.state = 'pending'
this.value = https://www.cnblogs.com/chscript/p/undefined
this.reason = undefined
this.resolveCallbacks = []
this.rejectCallbacks = []
try {
func(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
resolve = (value) => {
if (this.state ==='pending') {
this.state = 'fulfilled'
this.value = https://www.cnblogs.com/chscript/p/value
while (this.resolveCallbacks.length > 0) {
this.resolveCallbacks.shift().call(undefined)
}
}
}
reject = (reason) => {
if (this.state ==='pending') {
this.state = 'rejected'
this.reason = reason
while (this.rejectCallbacks.length > 0) {
this.rejectCallbacks.shift().call(undefined)
}
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value =https://www.cnblogs.com/chscript/p/> value
onRejected = typeof onRejected ==='function' ? onRejected : reason => { throw reason }
const p2 = new myPromise((resolve, reject) => {
if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'fulfilled') {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'pending') {
this.resolveCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.rejectCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return p2
}
}
function resolvePromise(p2, x, resolve, reject) {
if (x === p2) {
reject(new TypeError())
} else if ((x !== null && typeof x === 'object') || typeof x === 'function') {
let called
try {
let then = x.then
if (typeof then === 'function') {
then.call(x,
(y) => {
if (called) { return }
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) => {
if (called) { return }
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) { return }
called = true
reject(e)
}
} else {
resolve(x)
}
}
// 新加入部分
myPromise.deferred = function () {
let result = {};
result.promise = new myPromise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = myPromise;
新建一個檔案夾,放入我們的 myPromise.js 并在終端執行以下命令:
npm init -y
npm install promises-aplus-tests
package.json 檔案修改如下:
{
"name": "promise",
"version": "1.0.0",
"description": "",
"main": "myPromise.js",
"scripts": {
"test": "promises-aplus-tests myPromise"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
}
}
開始測驗我們的手寫 Promise,在終端執行以下命令即可:
npm test
Promise 其他方法補充
容錯處理方法
Promise.prototype.catch()
catch(onRejected) {
return this.then(undefined, onRejected)
}
Promise.prototype.finally()
finally(callback) {
return this.then(
value =https://www.cnblogs.com/chscript/p/> {
return myPromise.resolve(callback()).then(() => value)
},
reason => {
return myPromise.resolve(callback()).then(() => { throw reason })
}
)
}
靜態方法
Promise.resolve()
static resolve(value) {
if (value instanceof myPromise) {
return value // 傳入的引數為Promise實體物件,直接回傳
} else {
return new myPromise((resolve, reject) => {
resolve(value)
})
}
}
Promise.reject()
static reject(reason) {
return new myPromise((resolve, reject) => {
reject(reason)
})
}
Promise.all()
static all(promises) {
return new myPromise((resolve, reject) => {
let countPromise = 0 // 記錄傳入引數是否為Promise的次數
let countResolve = 0 // 記錄陣列中每個Promise被解決次數
let result = [] // 存盤每個Promise的解決或拒絕的值
if (promises.length === 0) { // 傳入的引數是一個空的可迭代物件
resolve(promises)
}
promises.forEach((element, index) => {
if (element instanceof myPromise === false) { // 傳入的引數不包含任何 promise
++countPromise
if (countPromise === promises.length) {
queueMicrotask(() => {
resolve(promises)
})
}
} else {
element.then(
value =https://www.cnblogs.com/chscript/p/> {
++countResolve
result[index] = value
if (countResolve === promises.length) {
resolve(result)
}
},
reason => {
reject(reason)
}
)
}
})
})
}
Promise.race()
static race(promises) {
return new myPromise((resolve, reject) => {
if (promises.length !== 0) {
promises.forEach(element => {
if (element instanceof myPromise === true)
element.then(
value =https://www.cnblogs.com/chscript/p/> {
resolve(value)
},
reason => {
reject(reason)
}
)
})
}
})
}
上述所有實作代碼已放置我的Github倉庫,可自行下載測驗,做更多優化,
[ https://github.com/chscript/myPromiseA- ]
參考
MDN-Promise
[譯]Promise/A+ 規范
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/540549.html
標籤:JavaScript
下一篇:遞回深拷貝
