JS手搓P(guān)romise的常見方法總結(jié)
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 的回調(diào)函數(shù) this.resolvedCallbacks = []; // 用于保存 reject 的回調(diào)函數(shù) this.rejectedCallbacks = []; // 狀態(tài)轉(zhuǎn)變?yōu)?resolved 方法 function resolve(value) { // 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個(gè)狀態(tài)改變后再進(jìn)行改變 if (value instanceof MyPromise) { return value.then(resolve, reject); } // 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾 setTimeout(() => { // 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變, if (self.state === PENDING) { // 修改狀態(tài) self.state = RESOLVED; // 設(shè)置傳入的值 self.value = value; // 執(zhí)行回調(diào)函數(shù) self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 狀態(tài)轉(zhuǎn)變?yōu)?rejected 方法 function reject(value) { // 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾 setTimeout(() => { // 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變 if (self.state === PENDING) { // 修改狀態(tài) self.state = REJECTED; // 設(shè)置傳入的值 self.value = value; // 執(zhí)行回調(diào)函數(shù) self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 將兩個(gè)方法傳入函數(shù)執(zhí)行 try { fn(resolve, reject); } catch (e) { // 遇到錯(cuò)誤時(shí),捕獲錯(cuò)誤,執(zhí)行 reject 函數(shù) reject(e); } } MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判斷兩個(gè)參數(shù)是否為函數(shù)類型,因?yàn)檫@兩個(gè)參數(shù)是可選參數(shù) onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; }; // 如果是等待狀態(tài),則將函數(shù)加入對(duì)應(yīng)列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); } // 如果狀態(tài)已經(jīng)凝固,則直接執(zhí)行對(duì)應(yīng)狀態(tài)的函數(shù) if (this.state === RESOLVED) { onResolved(this.value); } if (this.state === REJECTED) { onRejected(this.value); } };
2. 手搓 Promise.then
then
方法返回一個(gè)新的 promise
實(shí)例,為了在 promise
狀態(tài)發(fā)生變化時(shí)(resolve
/ reject
被調(diào)用時(shí))再執(zhí)行 then
里的函數(shù),我們使用一個(gè) callbacks
數(shù)組先把傳給then的函數(shù)暫存起來,等狀態(tài)改變時(shí)再調(diào)用。
那么,怎么保證后一個(gè) **then** 里的方法在前一個(gè) **then**(可能是異步)結(jié)束之后再執(zhí)行呢?
我們可以將傳給 then
的函數(shù)和新 promise
的 resolve
一起 push
到前一個(gè) promise
的 callbacks
數(shù)組中,達(dá)到承前啟后的效果:
- 承前:當(dāng)前一個(gè)
promise
完成后,調(diào)用其resolve
變更狀態(tài),在這個(gè)resolve
里會(huì)依次調(diào)用callbacks
里的回調(diào),這樣就執(zhí)行了then
里的方法了 - 啟后:上一步中,當(dāng)
then
里的方法執(zhí)行完成后,返回一個(gè)結(jié)果,如果這個(gè)結(jié)果是個(gè)簡(jiǎn)單的值,就直接調(diào)用新promise
的resolve
,讓其狀態(tài)變更,這又會(huì)依次調(diào)用新promise
的callbacks
數(shù)組里的方法,循環(huán)往復(fù)。。如果返回的結(jié)果是個(gè)promise
,則需要等它完成之后再觸發(fā)新promise
的resolve
,所以可以在其結(jié)果的then
里調(diào)用新promise
的resolve
then(onFulfilled, onReject){ // 保存前一個(gè)promise的this const self = this; return new MyPromise((resolve, reject) => { // 封裝前一個(gè)promise成功時(shí)執(zhí)行的函數(shù) let fulfilled = () => { try{ const result = onFulfilled(self.value); // 承前 return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //啟后 }catch(err){ reject(err) } } // 封裝前一個(gè)promise失敗時(shí)執(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ù)多個(gè)
then
里的回調(diào)方法是同步注冊(cè)的,但注冊(cè)到了不同的callbacks
數(shù)組中,因?yàn)槊看?nbsp;then
都返回新的promise
實(shí)例(參考上面的例子和圖) - 注冊(cè)完成后開始執(zhí)行構(gòu)造函數(shù)中的異步事件,異步完成之后依次調(diào)用
callbacks
數(shù)組中提前注冊(cè)的回調(diào)
3. 手搓 Promise.all
1) 核心思路
- 接收一個(gè) Promise 實(shí)例的數(shù)組或具有 Iterator 接口的對(duì)象作為參數(shù)
- 這個(gè)方法返回一個(gè)新的 promise 對(duì)象,
- 遍歷傳入的參數(shù),用Promise.resolve()將參數(shù)"包一層",使其變成一個(gè)promise對(duì)象
- 參數(shù)所有回調(diào)成功才是成功,返回值數(shù)組與參數(shù)順序一致
- 參數(shù)數(shù)組其中一個(gè)失敗,則觸發(fā)失敗狀態(tài),第一個(gè)觸發(fā)失敗的 Promise 錯(cuò)誤信息作為 Promise.all 的錯(cuò)誤信息。
2)實(shí)現(xiàn)代碼
一般來說,Promise.all 用來處理多個(gè)并發(fā)請(qǐng)求,也是為了頁面數(shù)據(jù)構(gòu)造的方便,將一個(gè)頁面所用到的在不同接口的數(shù)據(jù)一起請(qǐng)求過來,不過,如果其中一個(gè)接口失敗了,多個(gè)請(qǐng)求也就失敗了,頁面可能啥也出不來,這就看當(dāng)前頁面的耦合程度了
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í)例數(shù)組, 然后其 then 注冊(cè)的回調(diào)方法是數(shù)組中的某一個(gè) Promise 的狀態(tài)變?yōu)?fulfilled 的時(shí)候就執(zhí)行. 因?yàn)?Promise 的狀態(tài)只能改變一次, 那么我們只需要把 Promise.race 中產(chǎn)生的 Promise 對(duì)象的 resolve 方法, 注入到數(shù)組中的每一個(gè) Promise 實(shí)例中的回調(diào)函數(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) } }) }
到此這篇關(guān)于JS手搓P(guān)romise的常見方法總結(jié)的文章就介紹到這了,更多相關(guān)JS Promise內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家
相關(guān)文章
JS正則截取兩個(gè)字符串之間及字符串前后內(nèi)容的方法
這篇文章主要介紹了JS正則截取兩個(gè)字符串之間及字符串前后內(nèi)容的方法,結(jié)合實(shí)例形式簡(jiǎn)單分析了JS正則截取字符串操作的常用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01uploadify在Firefox下丟失session問題的解決方法
在用uploadify上傳插件時(shí)遇到了一個(gè)問題,在讀session時(shí)認(rèn)為沒有權(quán)限而被攔截了,后來在后臺(tái)打印登錄時(shí)產(chǎn)生session的id和上傳時(shí)讀取session的id,解決方法如下,感興趣的朋友可以了解下2013-08-08javascript數(shù)組中的reduce方法和pop方法
這篇文章主要介紹了javascript數(shù)組中的reduce方法和pop方法,文章內(nèi)容介紹詳細(xì),具有一定的參考價(jià)值需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助2022-03-03