Promise概覽
Promise是管理非同步程式設計的方案,它是建構函數,每次使用可用new建立實例;它有三種狀態:pending、fulfilled和rejected ,這三種狀態不會受外界影響,狀態只能由pending變成fullfilled(成功),pending變成rejected(失敗),而且一旦改變就不會再改變,在狀態改變後,它會回傳成功的結果或失敗的原因,它對外拋出了resolve、reject、catch、finally、then、all、race、done,在最新的提案中,添加了allSettled方法,它不管成功、失敗都會返回,接下來,我們自己實作整個Promise
executor函數
我們知道,在建立一個Promise實例時,都會立即執行executor函數,executor函數傳遞兩個參數,resolve和reject ,如果executor函數執行錯誤,Promise實例狀態會變成rejected
class MyPromise{ constructor(executor) { this.status = "pending"; // 初始化状态为pending this.value = undefined; // 初始化返回的成功的结果或者失败的原因 // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { if(this.status !== "pending") return; // 状态一旦改变,就不会再变 this.status = "resolved"; this.value = result; } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { if(this.status !== "pending") return; this.status = "rejected"; this.value = reason; } // try、catch捕获异常,如果错误,执行reject方法 try { executor(resolve, reject) } catch(err) { reject(err) } } }
我們來驗證一下,現在的Promise是什麼樣的
let p1 = new MyPromise((resolve, reject) => { resolve(1); }) let p2 = new MyPromise((resolve, reject) => { reject(2); }) console.log(p1); console.log(p2);
可以看到,狀態已經改變了,裡面的值也是成功的結果和失敗的原因。 then方法有兩個參數,第一個參數是成功時執行的,第二個參數為失敗後執行的,then的鍊式呼叫和數組等是一樣的,每次執行後會回傳一個Promise實例。如果成功後,第一個then中成功的函數為null,它會繼續向下查找,直到不為null的函數執行,上一個then中傳回的結果會直接影響下一個then中執行成功或失敗的哪一個函數,了解了這些之後,我們嘗試實現~
then方法
then(resolveFn, rejectFn) { // 如果传入的两个参数不是函数,则直接执行返回结果 let resolveArr = []; let rejectArr = []; if(typeof resolveFn !== "function") { resolveFn = result => { return result; } } if(typeof rejectFn !== "function") { rejectFn = reason => { return MyPromise.reject(reason); } } return new Mypromise((resolve, reject) => { resolveArr.push(result => { try { let x = resolveFn(result); if(x instanceof MyPromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) rejectArr.push(reason => { try { let x = rejectFn(reason); if(x instanceof MyPromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) }) }
我們來整理上面的程式碼
class MyPromise{ constructor(executor) { this.status = "pending"; // 初始化状态为pending this.value = undefined; // 初始化返回的成功的结果或者失败的原因 this.resolveArr = []; // 初始化then中成功的方法 this.rejectArr = []; // 初始化then中失败的方法 // 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多 let change = (status, value) => { if(this.status !== "pending") return; // 状态一旦改变,就不会再变 this.status = status; this.value = value; // 根据状态判断要执行成功的方法或失败的方法 let fnArr = status === "resolved" ? this.resolveArr : this.rejectArr; // fnArr中的方法依次执行 fnArr.forEach(item => { if(typeof item !== "function") return; item(this. value); }) } // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { change("resolved", result) } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { change("rejected", reason); } // try、catch捕获异常,如果错误,执行reject方法 try { executor(resolve, reject) } catch(err) { reject(err) } } then(resolveFn, rejectFn) { // 如果传入的两个参数不是函数,则直接执行返回结果 if(typeof resolveFn !== "function") { resolveFn = result => { return result; } } if(typeof rejectFn !== "function") { rejectFn = reason => { return MyPromise.reject(reason); } } return new MyPromise((resolve, reject) => { this.resolveArr.push(result => { try { let x = resolveFn(result); // 获取执行成功方法返回的结果 // 如果x是一个promise实例,则继续调用then方法 ==> then链的实现 if(x instanceof MyPromise) { x.then(resolve, reject) return; } // 不是promise实例,直接执行成功的方法 resolve(x); } catch(err) { reject(err) } }) this.rejectArr.push(reason => { try { let x = rejectFn(reason); if(x instanceof MyPromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) }) } }
我們來看一下效果
new MyPromise((resolve, reject) => { resolve(1); }).then(res => { console.log(res, 'success'); }, err => { console.log(err, 'error'); })
這時候,問題出現了,我們發現好像什麼都沒有輸出,如果我們對上面的測試範例做一下小小的改動呢?
new MyPromise((resolve, reject) => { setTimeout(_ => { resolve(1); }, 0) }).then(res => { console.log(res, 'success'); // 1 "success" }, err => { console.log(err, 'error'); })
這是因為建立了Promise實例就立即執行了executor函數,還沒有執行then方法,那麼不管成功或失敗的陣列中,都是空的。那可能朋友又有疑問了,為什麼加了setTimeout就好使了呢?這是因為在事件佇列機制中,setTimeout會放入事件佇列中,等主執行緒執行完成後再執行,此時then方法會儲存成功或失敗的函數,所以不管是成功的陣列或失敗的陣列中都已經有值了,這個時候再去執行就完全了~
但是我們不能在使用的時候寫setTimeout當做解決方案呀,既然我們在封裝,就要在封裝的函數內解決問題,按照這樣的思路,我們也同樣可以在resolve和reject方法執行的時候,判斷數組中是否有值,如果沒有,我們可以利用setTimeout讓它延後執行,代碼如下~
// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { // 如果数组中有值,则立即改变状态 if(this.resolveArr.length > 0) { change("resolved", result) } // 如果没值,则延后改变状态 let timer = setTimeout(_ => { change("resolved", result) clearTimeout(timer); }, 0) } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { // 如果数组中有值,则立即改变状态 if(this.rejectArr.length > 0) { change("rejected", reason); } // 如果没值,则延后改变状态 let timer = setTimeout(_ => { change("rejected", reason); clearTimeout(timer); }, 0) }
現在我們再試試
// 1、已经成功了 new MyPromise((resolve, reject) => { resolve('我成功啦,吼吼吼~~~~'); reject('我都已经成功了,你别想让我失败,哼~~'); }).then(res => { console.log(res, 'success'); // 我成功啦,吼吼吼~~~~ success }, err => { console.log(err, 'error'); }) // 2、先失败了 new MyPromise((resolve, reject) => { reject('失败了,我好委屈,呜呜呜~~'); resolve('已经失败了~~~'); }).then(res => { console.log(res, 'success'); }, err => { console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error }) // 3、链式调用 new MyPromise((resolve, reject) => { reject('失败了,我好委屈,呜呜呜~~'); resolve('已经失败了~~~'); }).then(res => { console.log(res); }, err => { console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error return '我要发奋图强,不会被困难所击倒,我要成功!!!' }).then(res1 => { console.log(res1, '经过不懈努力,我终于在第二次成功了~'); // 我要发奋图强,不会被困难所击倒,我要成功!!! 经过不懈努力,我终于在第二次成功了~ }, err1 => { console.log(err1, '第二次失败'); })
這就完美解決了第一次調用,不會執行then方法的問題。同時,實作了鍊式的呼叫。對於鍊式的調用,我多囉嗦兩句,其實不管是數組的鍊式調用,都是因為上次返回的還是此實例。
catch方法
catch方法是捕獲異常,它和then方法的第二個回呼函數是一樣的
catch(rejectFn) { return this.then(null, rejectFn) }
resolve方法
我們知道,Promsie也可以這樣用
let p1 = MyPromise.resolve(1); console.log(p1);
我們期望有這樣一種寫法,但現在肯定會拋出錯誤:MyPromise.resolve不是一個方法
現在需要我們封裝一下resolve方法,我們需要明確的是,resolve之後,Promise是支援再繼續鍊式呼叫then的,所以,我們需要執行resolve方法,回傳一個Promise實例
static resolve(result) { // 返回新的promise实例,执行promise实例中resolve方法 return new MyPromise(resolve => { resolve(result) }) }
reject方法
像resolve方法一樣,只不過它接收的是失敗的函數
static reject(reason) { // 返回新的promise实例,执行promise实例中reject方法 return new MyPromise((_, reject) => { reject(reason); }) }
done方法
##ES6在標準入門一書中,對done方法的解釋是這樣的:無論Promise物件的回呼鏈以then方法還是catch方法結尾,只要最後一個方法拋出錯誤,都有可能無法捕獲。為此,Promise提供了一個done方法,它總是處於回掉鏈的尾端,保證拋出任何可能出現的錯誤。好了,我們知道這個方法是乾啥的,現在就開始寫吧~done(resolveFn, rejectFn) { this.then(resolveFn, rejectFn) .catch(reason => { setTimeout(() => { throw reason; }, 0) }) }它可以接收了fulfilled、rejected狀態的回呼函數,也可以不提供任何參數。但是無論如何,done方法都會捕捉到任何可能出現的錯誤,並向全域拋出
finally方法
finally方法是無論成功還是失敗都會執行的方法,像這樣的方法還有小程式中的complete方法等等,我們來嘗試實作一下~finally(finallyFn) { let P = this.constructor; return this.then( value => P.resolve(finallyFn()).then(() => value), reason => P.reject(finallyFn()).then(() => reason) ) }我們來驗證一下
new MyPromise((resolve, reject) => { reject('失败了,我好委屈,呜呜呜~~'); resolve('已经失败了~~~'); }).then(res => { console.log(res); }, err => { console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error return '我要发奋图强,不会被困难所击倒,我要成功!!!' }).finally(() => { console.log('执行了吗'); // 这里会输出"执行了吗" })
all方法
all方法接收一個數組,當數組中每個實例都成功時才會返回,返回的也是一個數組,每個參數為對應的promise返回的結果,如果有一項失敗了,all方法都會回傳失敗// 接收数组参数 static all(promiseList) { // 返回新实例,调用后还可使用then、catch等方法 return new MyPromise((resolve, reject) => { let index = 0, // 成功次数计数 results = []; // 返回的结果 for(let i = 0; i < promiseList.length; i++) { let item = promiseList[i]; // 如果item不是promise实例 if(!(item instanceof MyPromise)) return; item.then(result => { index++; results[i] = result; if(index === promiseList.length) { resolve(results); } }).catch(reason => { reject(reason); }) } }) }來驗證一下
// 1.有失败的情况 let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.all([p1, p2, p3]) .then(res => { console.log(res); }).catch(err => { console.log(err, 'err'); // 2 "err" }) // 2.无失败的情况 let p1 = MyPromise.resolve(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.all([p1, p2, p3]) .then(res => { console.log(res, 'success'); // [1, 2, 3] "success" }).catch(err => { console.log(err, 'err'); })
race方法
race方法同样接收一个数组参数,里面每一项是Promise实例,它返回最快改变状态的Promise实例方法的结果
static race(promiseList) { return new MyPromise((resolve, reject) => { promiseList.forEach(item => { if(!(item instanceof MyPromise)) return; item.then(result => { resolve(result); }).catch(err => { reject(err) }) }) }) } 复制代码验证 // 1. let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); // 1 'success' }).catch(err => { console.log(err, 'err'); }) // 2. let p1 = MyPromise.reject(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); }).catch(err => { console.log(err, 'err'); // 1 'err' }) // 3. let p1 = MyPromise.reject(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.reject(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); }).catch(err => { console.log(err, 'err'); // 1 'err' })
尝试实现allSettled方法
allSettled方法也是接收数组参数,但是它无论成功或者失败,都会返回
static allSettled(promiseList) { return new MyPromise((resolve, reject) => { let results = []; for(let i = 0; i < promiseList.length; i++) { let item = promiseList[i]; if(!(item instanceof MyPromise)) return; item.then(result => { results[i] = result; }, reason => { results[i] = reason; }) resolve(results); } }) } 复制代码验证 // 1. let p1 = MyPromise.resolve(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); // [1, 2, 3] 'success' }).catch(err => { console.log(err, 'err'); }) // 2. let p1 = MyPromise.reject(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.reject(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); // [1, 2, 3] 'success' }).catch(err => { console.log(err, 'err'); }) // 3. let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); // [1, 2, 3] 'success' }).catch(err => { console.log(err, 'err'); })
推荐教程:《JS教程》
以上是手寫JS實作Promise的詳細內容。更多資訊請關注PHP中文網其他相關文章!