摘要:不同的的實現(xiàn)需要可以相互調(diào)用,搞清楚了標(biāo)準(zhǔn)之后,開始動手吧構(gòu)造函數(shù)產(chǎn)生一個對象有很多種方法,構(gòu)造函數(shù)是看起來最面向?qū)ο蟮囊环N,而且原生實現(xiàn)也是使用的構(gòu)造函數(shù),因此我也決定使用構(gòu)造函數(shù)的方法。
-- What i can"t create, i don"t understant
前言實現(xiàn)Promise的目的是為了深入的理解Promies,以在項目中游刃有余的使用它。完整的代碼見gitHub
Promise標(biāo)準(zhǔn)Promise的標(biāo)準(zhǔn)有很多個版本,本文采用ES6原生Promise使用的Promise/A+標(biāo)準(zhǔn)。完整的Promise/A+標(biāo)準(zhǔn)見這里,總結(jié)如下:
promise具有狀態(tài)state(status),狀態(tài)分為pending, fulfilled(我比較喜歡叫做resolved), rejected。初始為pending,一旦狀態(tài)改變,不能再更改為其它狀態(tài)。當(dāng)promise為fulfilled時,具有value;當(dāng)promise為rejected時,具有reason;value和reason都是一旦確定,不能改變的。
promise具有then方法,注意了,只有then方法是必須的,其余常用的catch,race,all,resolve等等方法都不是必須的,其實這些方法都可以用then方便的實現(xiàn)。
不同的promise的實現(xiàn)需要可以相互調(diào)用
OK,搞清楚了promise標(biāo)準(zhǔn)之后,開始動手吧
Promise構(gòu)造函數(shù)產(chǎn)生一個對象有很多種方法,構(gòu)造函數(shù)是看起來最面向?qū)ο蟮囊环N,而且原生Promise實現(xiàn)也是使用的構(gòu)造函數(shù),因此我也決定使用構(gòu)造函數(shù)的方法。
首先,先寫一個大概的框架出來:
// 總所周知,Promise傳入一個executor,有兩個參數(shù)resolve, reject,用來改變promise的狀態(tài) function Promise(executor) { this.status = "pending" this.value = void 0 // 為了方便把value和reason合并 const resolve = function() {} const reject = function() {} executor(resolve, reject) }
很明顯,這個構(gòu)造函數(shù)還有很多問題們一個一個來看
resolve和reject并沒有什么卵用。
首先,用過promise的都知道,resolve和reject是用來改變promise的狀態(tài)的:
function Promise(executor) { this.status = "pending" this.value = void 0 // 為了方便把value和reason合并 const resolve = value => { this.value = value this.status = "resolved" } const reject = reason => { this.value = reason this.status = "rejected" } executor(resolve, reject) }
然后,當(dāng)resolve或者reject調(diào)用的時候,需要執(zhí)行在then方法里傳入的相應(yīng)的函數(shù)(通知)。有沒有覺得這個有點類似于事件(發(fā)布-訂閱模式)呢?
function Promise(executor) { this.status = "pending" this.value = void 0 // 為了方便把value和reason合并 this.resolveListeners = [] this.rejectListeners = [] // 通知狀態(tài)改變 const notify(target, val) => { target === "resolved" ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = value => { this.value = value this.status = "resolved" notify("resolved", value) } const reject = reason => { this.value = reason this.status = "rejected" notify("rejected", reason) } executor(resolve, reject) }
status和value并沒有做到一旦確定,無法更改。這里有兩個問題,一是返回的對象暴露了status和value屬性,并且可以隨意賦值;二是如果在executor里多次調(diào)用resolve或者reject,會使value更改多次。
第一個問題,如何實現(xiàn)只讀屬性:
function Promise(executor) { if (typeof executor !== "function") { throw new Error("Promise executor must be fucntion") } let status = "pending" // 閉包形成私有屬性 let value = void 0 ...... // 使用status代替this.value const resolve = val => { value = val status = "resolved" notify("resolved", val) } const reject = reason => { value = reason status = "rejected" notify("rejected", reason) } // 通過getter和setter設(shè)置只讀屬性 Object.defineProperty(this, "status", { get() { return status }, set() { console.warn("status is read-only") } }) Object.defineProperty(this, "value", { get() { return value }, set() { console.warn("value is read-only") } })
第二個問題,避免多次調(diào)用resolve、reject時改變value,而且標(biāo)準(zhǔn)里(2.2.2.3 it must not be called more than once)也有規(guī)定,then注冊的回調(diào)只能執(zhí)行一次。
const resolve = val => { if (status !== "pending") return // 避免多次運行 value = val status = "resolved" notify("resolved", val) }
then注冊的回調(diào)需要異步執(zhí)行。
說到異步執(zhí)行,對原生Promise有了解的同學(xué)都知道,then注冊的回調(diào)在Micro-task中,并且調(diào)度策略是,Macro-task中執(zhí)行一個任務(wù),清空所有Micro-task的任務(wù)。簡而言之,promise異步的優(yōu)先級更高。
其實,標(biāo)準(zhǔn)只規(guī)定了promise回調(diào)需要異步執(zhí)行,在一個“干凈的”執(zhí)行棧執(zhí)行,并沒有規(guī)定一定說要用micro-task,并且在低版本瀏覽器中,并沒有micro-task隊列。不過在各種promise的討論中,由于原生Promise的實現(xiàn),micro-task已經(jīng)成成為了事實標(biāo)準(zhǔn),而且promise回調(diào)在micro-task中也使得程序的行為更好預(yù)測。
在瀏覽器端,可以用MutationObserver實現(xiàn)Micro-task。本文利用setTimeout來簡單實現(xiàn)異步。
const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } // 異步執(zhí)行 setTimeout(() => { if (status !== "pending") return status = "resolved" value = val notify("resolved", val) }, 0) }
最后,加上錯誤處理,就得到了一個完整的Promise構(gòu)造函數(shù):
function Promise(executor) { if (typeof executor !== "function") { throw new Error("Promise executor must be fucntion") } let status = "pending" let value = void 0 const notify = (target, val) => { target === "resolved" ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } setTimeout(() => { if (status !== "pending") return status = "resolved" value = val notify("resolved", val) }, 0) } const reject = reason => { setTimeout(() => { if (status !== "pending") return status = "rejected" value = reason notify("rejected", reason) }, 0) } this.resolveListeners = [] this.rejectListeners = [] Object.defineProperty(this, "status", { get() { return status }, set() { console.warn("status is read-only") } }) Object.defineProperty(this, "value", { get() { return value }, set() { console.warn("value is read-only") } }) try { executor(resolve, reject) } catch (e) { reject(e) } }
總的來說,Promise構(gòu)造函數(shù)其實只干了一件事:執(zhí)行傳入的executor,并構(gòu)造了executor的兩個參數(shù)。
實現(xiàn)then方法首先需要確定的是,then方法是寫在構(gòu)造函數(shù)里還是寫在原型里。
寫在構(gòu)造函數(shù)了里有一個比較大的好處:可以像處理status和value一樣,通過閉包讓resolveListeners和rejectListeners成為私有屬性,避免通過this.rejectListeners來改變它。
寫在構(gòu)造函數(shù)里的缺點是,每一個promise對象都會有一個不同的then方法,這既浪費內(nèi)存,又不合理。我的選擇是寫在原型里,為了保持和原生Promise有一樣的結(jié)構(gòu)和接口。
ok,還是先寫一個大概的框架:
Promise.prototype.then = function (resCb, rejCb) { this.resolveListeners.push(resCb) this.rejectListeners.push(rejCb) return new Promise() }
隨后,一步一步的完善它:
then方法返回的promise需要根據(jù)resCb或rejCb的運行結(jié)果來確定狀態(tài)。
Promise.prototype.then = function (resCb, rejCb) { return new Promise((res, rej) => { this.resolveListeners.push((val) => { try { const x = resCb(val) res(x) // 以resCb的返回值為value來resolve } catch (e) { rej(e) // 如果出錯,返回的promise以異常為reason來reject } }) this.rejectListeners.push((val) => { try { const x = rejCb(val) res(x) // 注意這里也是res而不是rej哦 } catch (e) { rej(e) // 如果出錯,返回的promise以異常為reason來reject } }) }) }
ps:眾所周知,promise可以鏈?zhǔn)秸{(diào)用,說起鏈?zhǔn)秸{(diào)用,我的第一個想法就是返回this就可以了,但是then方法不可以簡單的返回this,而要返回一個新的promise對象。因為promise的狀態(tài)一旦確定就不能更改,而then方法返回的promise的狀態(tài)需要根據(jù)then回調(diào)的運行結(jié)果來決定。
如果resCb/rejCb返回一個promiseA,then返回的promise需要跟隨(adopt)promiseA,也就是說,需要保持和promiseA一樣的status和value。
this.resolveListeners.push((val) => { try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } }) this.rejectListeners.push((val) => { try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } })
如果then的參數(shù)不是函數(shù),需要忽略它,類似于這種情況:
new Promise(rs => rs(5)) .then() .then(console.log)
其實就是把value和狀態(tài)往后傳遞
this.resolveListeners.push((val) => { if (typeof resCb !== "function") { res(val) return } try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } }) // rejectListeners也是相同的邏輯
如果調(diào)用then時, promise的狀態(tài)已經(jīng)確定,相應(yīng)的回調(diào)直接運行
// 注意這里需要異步 if (status === "resolved") setTimeout(() => resolveCb(value), 0) if (status === "rejected") setTimeout(() => rejectCb(value), 0)
最后,就得到了一個完整的then方法,總結(jié)一下,then方法干了兩件事,一是注冊了回調(diào),二是返回一個新的promise對象。
// resolveCb和rejectCb是相同的邏輯,封裝成一個函數(shù) const thenCallBack = (cb, res, rej, target, val) => { if (typeof cb !== "function") { target === "resolve" ? res(val) : rej(val) return } try { const x = cb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } } Promise.prototype.then = function (resCb, rejCb) { const status = this.status const value = this.value let thenPromise thenPromise = new Promise((res, rej) => { /** * 這里不能使用bind來實現(xiàn)柯里畫,規(guī)范里規(guī)定了: * 2.2.5: onFulfilled and onRejected must be called as functions (i.e. with no this value)) */ const resolveCb = val => { thenCallBack(resCb, res, rej, "resolve", val) } const rejectCb = val => { thenCallBack(rejCb, res, rej, "reject", val) } if (status === "pending") { this.resolveListeners.push(resolveCb) this.rejectListeners.push(rejectCb) } if (status === "resolved") setTimeout(() => resolveCb(value), 0) if (status === "rejected") setTimeout(() => rejectCb(value), 0) }) return thenPromise }不同的Promise實現(xiàn)可以互相調(diào)用
首先要明白的是什么叫互相調(diào)用,什么情況下會互相調(diào)用。之前實現(xiàn)then方法的時候,有一條規(guī)則是:如果then方法的回調(diào)返回一個promiseA。then返回的promise需要adopt這個promiseA,也就是說,需要處理這種情況:
new MyPromise(rs => rs(5)) .then(val => { return Promise.resolve(5) // 原生Promise }) .then(val => { return new Bluebird(r => r(5)) // Bluebird的promise })
關(guān)于這個,規(guī)范里定義了一個叫做The Promise Resolution Procedure的過程,我們需要做的就是把規(guī)范翻譯一遍,并替代代碼中判斷promise的地方
const resolveThenable = (promise, x, resolve, reject) => { if (x === promise) { return reject(new TypeError("chain call found")) } if (x instanceof Promise) { return x.then(v => { resolveThenable(promise, v, resolve, reject) }, reject) } if (x === null || (typeof x !== "object" && typeof x !== "function")) { return resolve(x) } let called = false try { // 這里有一個有意思的技巧。標(biāo)準(zhǔn)里解釋了,如果then是一個getter,那么通過賦值可以保證getter只被觸發(fā)一次,避免副作用 const then = x.then if (typeof then !== "function") { return resolve(x) } then.call(x, v => { if (called) return called = true resolveThenable(promise, v, resolve, reject) }, r => { if (called) return called = true reject(r) }) } catch (e) { if (called) return reject(e) } }
到這里,一個符合標(biāo)準(zhǔn)的Promise就完成了,完整的代碼如下:
function Promise(executor) { if (typeof executor !== "function") { throw new Error("Promise executor must be fucntion") } let status = "pending" let value = void 0 const notify = (target, val) => { target === "resolved" ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } setTimeout(() => { if (status !== "pending") return status = "resolved" value = val notify("resolved", val) }, 0) } const reject = reason => { setTimeout(() => { if (status !== "pending") return status = "rejected" value = reason notify("rejected", reason) }, 0) } this.resolveListeners = [] this.rejectListeners = [] Object.defineProperty(this, "status", { get() { return status }, set() { console.warn("status is read-only") } }) Object.defineProperty(this, "value", { get() { return value }, set() { console.warn("value is read-only") } }) try { executor(resolve, reject) } catch (e) { reject(e) } } const thenCallBack = (cb, res, rej, target, promise, val) => { if (typeof cb !== "function") { target === "resolve" ? res(val) : rej(val) return } try { const x = cb(val) resolveThenable(promise, x, res, rej) } catch (e) { rej(e) } } const resolveThenable = (promise, x, resolve, reject) => { if (x === promise) { return reject(new TypeError("chain call found")) } if (x instanceof Promise) { return x.then(v => { resolveThenable(promise, v, resolve, reject) }, reject) } if (x === null || (typeof x !== "object" && typeof x !== "function")) { return resolve(x) } let called = false try { // 這里有一個有意思的技巧。標(biāo)準(zhǔn)里解釋了,如果then是一個getter,那么通過賦值可以保證getter只被觸發(fā)一次,避免副作用 const then = x.then if (typeof then !== "function") { return resolve(x) } then.call(x, v => { if (called) return called = true resolveThenable(promise, v, resolve, reject) }, r => { if (called) return called = true reject(r) }) } catch (e) { if (called) return reject(e) } } Promise.prototype.then = function (resCb, rejCb) { const status = this.status const value = this.value let thenPromise thenPromise = new Promise((res, rej) => { const resolveCb = val => { thenCallBack(resCb, res, rej, "resolve", thenPromise, val) } const rejectCb = val => { thenCallBack(rejCb, res, rej, "reject", thenPromise, val) } if (status === "pending") { this.resolveListeners.push(resolveCb) this.rejectListeners.push(rejectCb) } if (status === "resolved") setTimeout(() => resolveCb(value), 0) if (status === "rejected") setTimeout(() => rejectCb(value), 0) }) return thenPromise }
測試腳本
關(guān)于promise的一些零散知識Promise.resolve就是本文所實現(xiàn)的resolveThenable,并不是簡單的用來返回一個resolved狀態(tài)的函數(shù),它返回的promise對象的狀態(tài)也并不一定是resolved。
promise.then(rs, rj)和promise.then(rs).catch(rj)是有區(qū)別的,區(qū)別在于當(dāng)rs出錯時,后一種方法可以進行錯誤處理。
感想與總結(jié)實現(xiàn)Promise的過程其實并沒有我預(yù)想的那么難,所謂的Promise的原理我感覺就是類似于觀察者模式,so,不要有畏難情緒,我上我也行^_^。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97944.html
摘要:今天我們來自己手寫一個符合規(guī)范的庫。是異步編程的一種解決方案,比傳統(tǒng)的解決方案回調(diào)函數(shù)和事件更合理和更強大。我們可以看到,其實就是一個構(gòu)造函數(shù)。所以說我們的數(shù)組里存的是一個一個的的回調(diào)函數(shù),也就是一個一個。 今天我們來自己手寫一個符合PromiseA+規(guī)范的Promise庫。大家是不是很激動呢?? showImg(https://segmentfault.com/img/bV6t4Z?...
摘要:在將來的其他規(guī)范中可能會涉及這些沒有提及的內(nèi)容。它禁止被觸發(fā)多次。如果到了狀態(tài),那么所有的回調(diào)函數(shù)都必須按照他們原有的順序進行調(diào)用執(zhí)行。 概述 自從準(zhǔn)備晉級之后,就拖更了很久了,既然晉級弄完了,那么也恢復(fù)更新了。 在面試別人的過程中,發(fā)現(xiàn)基本上沒有人對整個Promise完全了解,因此希望通過這篇文章來幫助大家了解下Promise的全貌。本文的主要內(nèi)容是Promise/A+規(guī)范的譯文,主...
摘要:前端開發(fā)中的中的中,是一個構(gòu)造函數(shù),通過它創(chuàng)建一個錯誤對象。是核心對象,表示調(diào)用一個時發(fā)生的異常。將回調(diào)函數(shù)包裹一層接下來可以將統(tǒng)一進行處理。中的錯誤捕獲在以前,可以使用來處理捕獲的錯誤。研究結(jié)果在這里中的錯誤捕獲的源碼中,在關(guān) 本文首發(fā)于公眾號:符合預(yù)期的CoyPan 寫在前面 在前端項目中,由于JavaScript本身是一個弱類型語言,加上瀏覽器環(huán)境的復(fù)雜性,網(wǎng)絡(luò)問題等等,很容易...
摘要:結(jié)果證明,對于以上瀏覽器,在生產(chǎn)環(huán)境使用是可行的。后面可以跟對象,表示等待才會繼續(xù)向下執(zhí)行,如果被或拋出異常則會被外面的捕獲。,,都是現(xiàn)在和未來解決異步的標(biāo)準(zhǔn)做法,可以完美搭配使用。這也是使用標(biāo)準(zhǔn)一大好處。只允許外部傳入成功或失敗后的回調(diào)。 showImg(https://cloud.githubusercontent.com/assets/948896/10188666/bc9a53...
摘要:換句話說該靜態(tài)函數(shù)返回個處于狀態(tài)的對象。等價于構(gòu)造函數(shù)的靜態(tài)函數(shù),創(chuàng)建一個對象并以值作為參數(shù)調(diào)用句柄函數(shù)。等價于介紹構(gòu)造函數(shù)的靜態(tài)函數(shù),參數(shù)是對象組成的可迭代數(shù)據(jù)集合。 一、概述 ES2015 Promise函數(shù)是對PromiseA+標(biāo)準(zhǔn)的實現(xiàn),并且嚴(yán)格遵守該標(biāo)準(zhǔn)。 二、APIs 2.1 創(chuàng)建Promise對象 Promise構(gòu)造函數(shù)的參數(shù)是個包含兩個參數(shù)的函數(shù),并且該函數(shù)的參數(shù)分別對...
閱讀 781·2023-04-25 16:55
閱讀 2821·2021-10-11 10:59
閱讀 2086·2021-09-09 11:38
閱讀 1799·2021-09-03 10:40
閱讀 1495·2019-08-30 15:52
閱讀 1134·2019-08-30 15:52
閱讀 965·2019-08-29 15:33
閱讀 3505·2019-08-29 11:26