摘要:內部總體上分為兩種情況,一種是當前對象狀態已經變為或,此時則直接把響應的回調函數添加到異步隊列中,另一種情況是當前對象狀態還是,此時則把響應的回調函數依次添加到數組中。
今天,我帶著大家一步一步跟著規范實現一個自己的Promise,大家可以對照我的第二篇文章Promise介紹--規范篇或官方規范來一一學習。
Promise內部有三個固定的狀態,我們在文件中提前定義。
const PENDING = "pending" const FULFILLED = "fulfilled" const REJECTED = "rejected"
首先,是構造器constructor。
constructor(resolver){ this._status = PENDING; // 保存內部的狀態 this._result = undefined; // 保存promise對象fulfill或reject的最終結果 this._childArr = []; // 調用then方法創建的子promise對象 this._fulfillArr = []; // 調用then方法添加的onFulfilled方法 this._rejectArr = []; // 調用then方法添加的onRejected方法 if (resolver == noop) { return}; // then方法內部創建promise時間使用 // 如果resolver不是函數,則拋出TypeError錯誤 if (!isFunction(resolver)) { throw new TypeError("參數必須為function"); }; // 如果直接調用Promise而非通過new關鍵詞創建,同樣拋出TypeError錯誤 if (this instanceof Promise) { initPromise(this, resolver) } else { throw new TypeError("Promise不可以直接作為函數調用") } }
當參數傳遞正確時,才真正初始化Promise對象,我提到了一個多帶帶的函數initPromise中,如下:
function initPromise(promise, resolver){ // 當調用傳入的resolver函數拋出異常,則reject當前promise try { resolver(function(value){ // 封裝的內部函數,處理resolve(value),也就是Promise Resolution Procedure resolve(promise, value); }, function(reason){ // 封裝的內部函數,處理reject(reason) reject(promise, reason); }); } catch (e) { reject(promise, e); } }
當我們執行new Promise((resolve){resolve(5)})時,會走resolve(promise, value)。接下來我們實現一下Promise Resolution Procedure。
function resolve(promise, value){ // 2.3.1 如果promise和value指向同一對象 if (promise === value) { reject(promise, new TypeError("不可以resolve Promise實例本身")) // 2.3.2 如果value是一個promise } else if (value instanceof Promise) { // 2.3.2.2 如果value處于fulfilled狀態,則使用相同的value值fulfill promise。 if (value._status == FULFILLED) { fulfill(promise, value._result); // 2.3.2.3 如果value處于rejected狀態,則使用相同的reason值reject promise。 } else if (value._status == REJECTED) { reject(promise, value._result); // 2.3.2.1 如果value處于pending狀態,則promise同樣pending并直到value狀態改變。 // 重新把resolve(promise, value)添加到隊列,asyncCall封裝了一下異步調用 } else { asyncCall(resolve, [promise, value]); } // 2.3.3 如果x是一個object或function } else if (isObjectOrFunction(value)){ // 2.3.3.2 如果獲取value.then的值時拋出異常,則通過該異常reject promise try{ let then = value.then; // 2.3.3.1 使then等于value.then // 2.3.3.3 如果then是一個函數 if (isFunction(then)) { try{ handleThenable(promise, value, then); } catch (e) { reject(promise, e); } // 2.3.3.4 如果then不是一個函數 } else { fulfill(promise, value); } } catch (e) { reject(promise, e); } // 2.3.4 value不是對象或函數 } else { fulfill(promise, value); } }
因為value.then是函數時,處理情況同樣很多且比較雜亂,我多帶帶把這部分處理提取到handleThenable函數中。實現如下:
function handleThenable(promise, value, then){ let settled = false; // 是否fulfilled或rejected try { // 2.3.3.3 如果then是一個函數,則把value作為函數中this指向來調用它 then.call(value, (otherValue)=>{ // 2.3.3.3.3 if (settled) { return}; // 2.3.3.3.1 如果resolvePromise通過傳入y來調用,則執行resolve(promise, y) resolve(promise, otherValue); settled = true; }, (reason)=>{ // 2.3.3.3.3 if (settled) { return}; // 2.3.3.3.2 如果rejectPromise 通過傳入原因r來調用,則傳入r來reject promise reject(promise, reason); settled = true; }) // 2.3.3.3.4 如果調用then拋出異常e } catch (e) { // 2.3.3.3.4.1 如果resolvePromise或rejectPromise已經調用,則忽略 if (settled) { return}; settled = true; // 2.3.3.3.4.2 否則,則傳入e來reject promise reject(promise, e) } }
以上,基本就是完整的我對Promise Resolution Procedure的實現。
還有一個非常重要的方法就是then方法,接下來我們看一下它是如何實現的。then內部總體上分為兩種情況,一種是當前promise對象狀態已經變為fulfilled或rejected,此時則直接把響應的回調函數添加到異步隊列中,另一種情況是當前promise對象狀態還是pending,此時則把響應的回調函數依次添加到數組中。
then(onFulfilled, onRejected){ let child = new Promise(noop); // 如果當前對象狀態已經改變,則直接根據狀態調用響應的回調 if (this._status !== PENDING) { if (this._status == FULFILLED) { // 2.2.4 異步調用 asyncCall(()=>{ dealThen(this, child, onFulfilled); }) } else { // 2.2.4 異步調用 asyncCall(()=>{ dealThen(this, child, onRejected); }) } // 如果當前對象處于pending狀態,則把onFulfilled, onRejected添加到 } else { this._childArr.push(child); this._fulfillArr.push(onFulfilled); this._rejectArr.push(onRejected); } // 返回一個新的promise對象 return child; }
具體處理邏輯我放到了一個新的函數dealThen中,注意它是異步調用的。所以用asyncCall方法包裝了一下。
// 處理then function dealThen(promise, child, x){ // onFulfilled或onRejected是一個函數 if (isFunction(x)) { // 2.2.7.1 如果onFulfilled或onRejected返回了一個值value,則執行resolve(child, value) try { resolve(child, x(promise._result)); // 2.2.7.2 如果onFulfilled或onRejected拋出了異常e,則reject child并傳入原因e } catch (e) { reject(child, e); } } else { try{ // 2.2.1.1 如果onFulfilled不是一個函數,則忽略 if (promise._status == FULFILLED) { fulfill(child, promise._result); // 2.2.1.2 如果onRejected不是一個函數,則忽略 } else { reject(child, promise._result); } } catch (e) { reject(child, e); } } }
從上面的代碼中我們看到有兩個比較重要的方法——fulfill和reject,它們才是真正改變promise狀態并調用相應回調的地方。
function fulfill(promise, value){ // 如果狀態已經不是pending,則直接return if (promise._status !== PENDING) { return }; // 設置狀態為fulfilled,并設置最終結果 promise._status = FULFILLED; promise._result = value; // 異步依次調用添加的onFulfilled方法 if (promise._fulfillArr.length > 0) { // 2.2.6.1 如果promise fulfilled,則所有的onFulfilled回調函數按照它們添加的順序依次調用。 promise._fulfillArr.forEach((k,index)=>{ // 2.2.5 onFulfilled和onRejected必須作為函數來調用,沒有this值 asyncCall(dealThen, [promise, promise._childArr[index], k]) }); } } function reject(promise, reason){ // 如果狀態已經不是pending,則直接return if (promise._status !== PENDING) { return }; // 設置狀態為rejected,并設置最終結果 promise._status = REJECTED; promise._result = reason; // 異步依次調用添加的onRejected方法 if (promise._rejectArr.length > 0) { // 2.2.6.2 如果promise rejected,則所有的onRejected回調函數按照它們添加的順序依次調用。 promise._rejectArr.forEach((k,index)=>{ // 2.2.5 onFulfilled和onRejected必須作為函數來調用,沒有this值 asyncCall(dealThen, [promise, promise._childArr[index], k]) }); } }
當然,還有一個實例方法catch,其實它調用的也是then方法。
catch(onRejected){ return this.then(undefined, onRejected); }
當然,Promise還有四個實例方法,分別如下:
resolve
Promise.resolve = function(value){ return new Promise(function(resolve){resolve(value)}) }
reject
Promise.reject = function(reason){ return new Promise(function(resolve, reject){reject(reason)}) }
all和race的實現沒有太好好思考,也沒有跑測試,不知道有沒有問題,而且個人感覺實現的也比較挫,是用setInterval一直輪詢查看每一個promise實例狀態有沒有改變,所以就不show code了。主要內容還是講Promises/A+的實現。
完整的代碼見我的github。
當然也有幾個問題,異步我是用setTimeout實現的,它屬于macro-task,而原生的Promise屬于micro-task,所以這里還有待改進。
另外,在上面的實現中,我們發現resolve(promise, value)中,在對2.3.2.1 如果value處于pending狀態,則promise同樣pending并直到value狀態改變。我基本采用的也是setTimeout
輪詢的方式實現的。之后看了es-promise的實現,因為根據2.3.2.1此時promise的狀態和value是同步的,所以可以把resolve和reject promise分別放在value相應狀態的回調中,并假設此時與之對應的value的child是undefined。如下所示:
// asyncCall(resolve, [promise, value]); value._childArr.push(undefined); value._fulfillArr.push((value)=>{resolve(promise, value)}); // ① value._rejectArr.push((reason)=>{reject(promise, reason)}); // ②
此時我們還需要對fulfill和reject兩個方法稍作改動。
function fulfill(promise, value){ if (promise._status !== PENDING) { return }; promise._status = FULFILLED; promise._result = value; if (promise._fulfillArr.length > 0) { promise._fulfillArr.forEach((k,index)=>{ // 如果對應的child不是undefined,則異步調用回調 if (promise._childArr[index]) { asyncCall(dealThen, [promise, promise._childArr[index], k]) // 如果對應的child是undefined,則直接執行回調函數①并傳入value } else { k(value); } }); } } function reject(promise, reason){ if (promise._status !== PENDING) { return }; promise._status = REJECTED; promise._result = reason; if (promise._rejectArr.length > 0) { promise._rejectArr.forEach((k,index)=>{ // 如果對應的child不是undefined,則異步調用回調 if (promise._childArr[index]) { asyncCall(dealThen, [promise, promise._childArr[index], k]) // 如果對應的child是undefined,則直接執行回調函數②并傳入reason } else { k(reason); } }); }; }
修改版見Promise1。
錯誤或不足之處,歡迎指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/91309.html
摘要:我們稱為回調對象,它內部會維護一個數組,我們可以向其中添加若干個回調函數,然后在某一條件下觸發執行。第一次之后,再次新的回調函數時,自動執行回調。當前面的回調函數返回時,終止后面的回調繼續執行。 最近懶癌發作,說好的系列文章,寫了一半,一直懶得寫,今天補上一篇。 Deferred 我們在使用promise對象時,總會提到一個與它關系密切的對象——Deferred。其實Deferred沒...
摘要:規范中對于構造函數沒有明確說明,所以在此處拿出來講解一下。構造函數只接收一個參數,且該參數必須是一個函數,任何其他的值比如等都會報一個的錯誤。 本篇文章是Promise系列文章的第二篇,主要是講解基于Promise/A+規范,在傳入不同類型的參數時,promise內部分別會如何處理。本章的主要目的是讓大家對promise有一個更加深入的理解,也為下一篇講如何實現一個promise庫做準...
摘要:請求的傳統寫法改為后的寫法很顯然,我們把異步中使用回調函數的場景改為了等函數鏈式調用的方式。數組中第一個元素是異步的,第二個是非異步,會立即改變狀態,所以新對象會立即改變狀態并把傳遞給成功時的回調函數。 前言 Promise,相信每一個前端工程師都或多或少地在項目中都是用過,畢竟它早已不是一個新名詞。ES6中已經原生對它加以支持,在caniuse中搜索一下Promise,發現新版的ch...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:因此,當作為參數的執行任意結果的回調函數時,就會將參數傳遞給外層的,執行對應的回調函數。 背景 在上一篇博客[[譯]前端基礎知識儲備——Promise/A+規范](https://segmentfault.com/a/11...,我們介紹了Promise/A+規范的具體條目。在本文中,我們來選擇了promiz,讓大家來看下一個具體的Promise庫的內部代碼是如何運作的。 promiz...
閱讀 2676·2023-04-25 18:10
閱讀 1617·2019-08-30 15:53
閱讀 2811·2019-08-30 13:10
閱讀 3228·2019-08-29 18:40
閱讀 1134·2019-08-23 18:31
閱讀 1209·2019-08-23 16:49
閱讀 3408·2019-08-23 16:07
閱讀 883·2019-08-23 15:27