JS手搓Promise的常見方法總結
1. 手搓 Promise
const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; function MyPromise(fn) { // 保存初始化狀態(tài) var self = this; // 初始化狀態(tài) this.state = PENDING; // 用于保存 resolve 或者 rejected 傳入的值 this.value = null; // 用于保存 resolve 的回調函數(shù) this.resolvedCallbacks = []; // 用于保存 reject 的回調函數(shù) this.rejectedCallbacks = []; // 狀態(tài)轉變?yōu)?resolved 方法 function resolve(value) { // 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個狀態(tài)改變后再進行改變 if (value instanceof MyPromise) { return value.then(resolve, reject); } // 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾 setTimeout(() => { // 只有狀態(tài)為 pending 時才能轉變, if (self.state === PENDING) { // 修改狀態(tài) self.state = RESOLVED; // 設置傳入的值 self.value = value; // 執(zhí)行回調函數(shù) self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 狀態(tài)轉變?yōu)?rejected 方法 function reject(value) { // 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾 setTimeout(() => { // 只有狀態(tài)為 pending 時才能轉變 if (self.state === PENDING) { // 修改狀態(tài) self.state = REJECTED; // 設置傳入的值 self.value = value; // 執(zhí)行回調函數(shù) self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 將兩個方法傳入函數(shù)執(zhí)行 try { fn(resolve, reject); } catch (e) { // 遇到錯誤時,捕獲錯誤,執(zhí)行 reject 函數(shù) reject(e); } } MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判斷兩個參數(shù)是否為函數(shù)類型,因為這兩個參數(shù)是可選參數(shù) onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; }; // 如果是等待狀態(tài),則將函數(shù)加入對應列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); } // 如果狀態(tài)已經凝固,則直接執(zhí)行對應狀態(tài)的函數(shù) if (this.state === RESOLVED) { onResolved(this.value); } if (this.state === REJECTED) { onRejected(this.value); } };
2. 手搓 Promise.then
then
方法返回一個新的 promise
實例,為了在 promise
狀態(tài)發(fā)生變化時(resolve
/ reject
被調用時)再執(zhí)行 then
里的函數(shù),我們使用一個 callbacks
數(shù)組先把傳給then的函數(shù)暫存起來,等狀態(tài)改變時再調用。
那么,怎么保證后一個 **then** 里的方法在前一個 **then**(可能是異步)結束之后再執(zhí)行呢?
我們可以將傳給 then
的函數(shù)和新 promise
的 resolve
一起 push
到前一個 promise
的 callbacks
數(shù)組中,達到承前啟后的效果:
- 承前:當前一個
promise
完成后,調用其resolve
變更狀態(tài),在這個resolve
里會依次調用callbacks
里的回調,這樣就執(zhí)行了then
里的方法了 - 啟后:上一步中,當
then
里的方法執(zhí)行完成后,返回一個結果,如果這個結果是個簡單的值,就直接調用新promise
的resolve
,讓其狀態(tài)變更,這又會依次調用新promise
的callbacks
數(shù)組里的方法,循環(huán)往復。。如果返回的結果是個promise
,則需要等它完成之后再觸發(fā)新promise
的resolve
,所以可以在其結果的then
里調用新promise
的resolve
then(onFulfilled, onReject){ // 保存前一個promise的this const self = this; return new MyPromise((resolve, reject) => { // 封裝前一個promise成功時執(zhí)行的函數(shù) let fulfilled = () => { try{ const result = onFulfilled(self.value); // 承前 return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //啟后 }catch(err){ reject(err) } } // 封裝前一個promise失敗時執(zhí)行的函數(shù) let rejected = () => { try{ const result = onReject(self.reason); return result instanceof MyPromise? result.then(resolve, reject) : reject(result); }catch(err){ reject(err) } } switch(self.status){ case PENDING: self.onFulfilledCallbacks.push(fulfilled); self.onRejectedCallbacks.push(rejected); break; case FULFILLED: fulfilled(); break; case REJECT: rejected(); break; } }) }
注意:
- 連續(xù)多個
then
里的回調方法是同步注冊的,但注冊到了不同的callbacks
數(shù)組中,因為每次then
都返回新的promise
實例(參考上面的例子和圖) - 注冊完成后開始執(zhí)行構造函數(shù)中的異步事件,異步完成之后依次調用
callbacks
數(shù)組中提前注冊的回調
3. 手搓 Promise.all
1) 核心思路
- 接收一個 Promise 實例的數(shù)組或具有 Iterator 接口的對象作為參數(shù)
- 這個方法返回一個新的 promise 對象,
- 遍歷傳入的參數(shù),用Promise.resolve()將參數(shù)"包一層",使其變成一個promise對象
- 參數(shù)所有回調成功才是成功,返回值數(shù)組與參數(shù)順序一致
- 參數(shù)數(shù)組其中一個失敗,則觸發(fā)失敗狀態(tài),第一個觸發(fā)失敗的 Promise 錯誤信息作為 Promise.all 的錯誤信息。
2)實現(xiàn)代碼
一般來說,Promise.all 用來處理多個并發(fā)請求,也是為了頁面數(shù)據構造的方便,將一個頁面所用到的在不同接口的數(shù)據一起請求過來,不過,如果其中一個接口失敗了,多個請求也就失敗了,頁面可能啥也出不來,這就看當前頁面的耦合程度了
function promiseAll(promises) { return new Promise(function(resolve, reject) { if(!Array.isArray(promises)){ throw new TypeError(`argument must be a array`) } var resolvedCounter = 0; var promiseNum = promises.length; var resolvedResult = []; for (let i = 0; i < promiseNum; i++) { Promise.resolve(promises[i]).then(value=>{ resolvedCounter++; resolvedResult[i] = value; if (resolvedCounter == promiseNum) { return resolve(resolvedResult) } },error=>{ return reject(error) }) } }) } // test let p1 = new Promise(function (resolve, reject) { setTimeout(function () { resolve(1) }, 1000) }) let p2 = new Promise(function (resolve, reject) { setTimeout(function () { resolve(2) }, 2000) }) let p3 = new Promise(function (resolve, reject) { setTimeout(function () { resolve(3) }, 3000) }) promiseAll([p3, p1, p2]).then(res => { console.log(res) // [3, 1, 2] })
4. 手搓 Promise.race
該方法的參數(shù)是 Promise 實例數(shù)組, 然后其 then 注冊的回調方法是數(shù)組中的某一個 Promise 的狀態(tài)變?yōu)?fulfilled 的時候就執(zhí)行. 因為 Promise 的狀態(tài)只能改變一次, 那么我們只需要把 Promise.race 中產生的 Promise 對象的 resolve 方法, 注入到數(shù)組中的每一個 Promise 實例中的回調函數(shù)中即可.
Promise.race = function (args) { return new Promise((resolve, reject) => { for (let i = 0, len = args.length; i < len; i++) { args[i].then(resolve, reject) } }) }
到此這篇關于JS手搓Promise的常見方法總結的文章就介紹到這了,更多相關JS Promise內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家
相關文章
uploadify在Firefox下丟失session問題的解決方法
在用uploadify上傳插件時遇到了一個問題,在讀session時認為沒有權限而被攔截了,后來在后臺打印登錄時產生session的id和上傳時讀取session的id,解決方法如下,感興趣的朋友可以了解下2013-08-08javascript數(shù)組中的reduce方法和pop方法
這篇文章主要介紹了javascript數(shù)組中的reduce方法和pop方法,文章內容介紹詳細,具有一定的參考價值需要的小伙伴可以參考一下,希望對你的學習有所幫助2022-03-03