摘要:可以理解為,先把中回調函數注冊到某個數組里,等執時候,再執行這個數組中的回調函數。方法注冊回調函數,也可以理解為發布訂閱模式。總結一下方法把中回調函數注冊給上一個對象中的數組中,并交由上一個對象處理。
前言
Promise作為一種異步處理的解決方案,以同步的寫作方式來處理異步代碼。本文只涉及Promise函數和then方法,對其他方法(如catch,finally,race等)暫不研究。首先,看下Promise的使用場景。
使用場景例1 普通使用
new Promise((resolve, reject) => { console.log(1); resolve(2); console.log(3); }).then(result => { console.log(result); }).then(data => { console.log(data); }); // => 1 // => 3 // => 2 // => undefined
構造函數Promise接受一個函數參數exactor,這個函數里有兩個函數參數(resolve和reject),在實例化之后,立即執行這個exactor,需要注意的是,exactor里面除了resolve和reject函數都是異步執行的,其他都是同步執行。
通過它的then方法【注冊】promise異步操作成功時執行的回調,意思就是resolve傳入的數據會傳遞給then中回調函數中的參數。可以理解為,先把then中回調函數注冊到某個數組里,等執resolve時候,再執行這個數組中的回調函數。如果then中的回調函數沒有返回值,那么下個then中回調函數的參數為undefined。 then方法注冊回調函數,也可以理解為【發布訂閱模式】。
例2 Promise與原生ajax結合使用
function getUrl(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest() xhr.open("GET", url, true); xhr.onload = function () { if (/^2d{2}$/.test(this.status) || this.status === 304 ) { resolve(this.responseText, this) } else { let reason = { code: this.status, response: this.response }; reject(reason, this); } }; xhr.send(null); }); } getUrl("./a.text") .then(data => {console.log(data)});
例3 Promise與$.ajax()結合使用
var getData=function(url) { return new Promise((resolve, reject) => { $.ajax({ type:"get", url:url, success:function(data){ resolve(data); }, error:function(err){ reject(err); } }); }); } getData("./a.txt") .then(data => { console.log(data); });Promise源碼分析
首先看一下Promise函數的源碼
function Promise(excutor) { let that = this; // 緩存當前promise實例對象 that.status = PENDING; // 初始狀態 that.value = undefined; // fulfilled狀態時 返回的信息 that.reason = undefined; // rejected狀態時 拒絕的原因 that.onFulfilledCallbacks = []; // 存儲fulfilled狀態對應的onFulfilled函數 that.onRejectedCallbacks = []; // 存儲rejected狀態對應的onRejected函數 function resolve(value) { // value成功態時接收的終值 if(value instanceof Promise) { return value.then(resolve, reject); } // 為什么resolve 加setTimeout? // 2.2.4規范 onFulfilled 和 onRejected 只允許在 execution context 棧僅包含平臺代碼時運行. // 注1 這里的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環之后的新執行棧中執行。 setTimeout(() => { // 調用resolve 回調對應onFulfilled函數 if (that.status === PENDING) { // 只能由pedning狀態 => fulfilled狀態 (避免調用多次resolve reject) that.status = FULFILLED; that.value = value; that.onFulfilledCallbacks.forEach(cb => cb(that.value)); } }); } function reject(reason) { // reason失敗態時接收的拒因 setTimeout(() => { // 調用reject 回調對應onRejected函數 if (that.status === PENDING) { // 只能由pedning狀態 => rejected狀態 (避免調用多次resolve reject) that.status = REJECTED; that.reason = reason; that.onRejectedCallbacks.forEach(cb => cb(that.reason)); } }); } // 捕獲在excutor執行器中拋出的異常 // new Promise((resolve, reject) => { // throw new Error("error in excutor") // }) try { excutor(resolve, reject); } catch (e) { reject(e); } }
根據上面代碼,Promise相當于一個狀態機,一共有三種狀態,分別是 pending(等待),fulfilled(成功),rejected(失敗)。
that.onFulfilledCallbacks這個數組就是存儲then方法中的回調函數。執行reject函數為什么是異步的,就是因為里面有setTimeout這個函數。當reject執行的時候,里面的 pending狀態->fulfilled狀態,改變之后無法再次改變狀態了。
然后執行onFulfilledCallbacks里面通過then注冊的回調函數。因為resolve執行的時候是異步的,所以還沒執行resolve里面具體的代碼時候,已經通過then方法,把then中回調函數給注冊到了
onFulfilledCallbacks中,所以才能夠執行onFulfilledCallbacks里面的回調函數。
我們看下then又是如何注冊回調函數的
/** * [注冊fulfilled狀態/rejected狀態對應的回調函數] * @param {function} onFulfilled fulfilled狀態時 執行的函數 * @param {function} onRejected rejected狀態時 執行的函數 * @return {function} newPromsie 返回一個新的promise對象 */ Promise.prototype.then = function(onFulfilled, onRejected) { const that = this; let newPromise; // 處理參數默認值 保證參數后續能夠繼續執行 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason; }; // then里面的FULFILLED/REJECTED狀態時 為什么要加setTimeout ? // 原因: // 其一 2.2.4規范 要確保 onFulfilled 和 onRejected 方法異步執行(且應該在 then 方法被調用的那一輪事件循環之后的新執行棧中執行) 所以要在resolve里加上setTimeout // 其二 2.2.6規范 對于一個promise,它的then方法可以調用多次.(當在其他程序中多次調用同一個promise的then時 由于之前狀態已經為FULFILLED/REJECTED狀態,則會走的下面邏輯),所以要確保為FULFILLED/REJECTED狀態后 也要異步執行onFulfilled/onRejected // 其二 2.2.6規范 也是resolve函數里加setTimeout的原因 // 總之都是 讓then方法異步執行 也就是確保onFulfilled/onRejected異步執行 // 如下面這種情景 多次調用p1.then // p1.then((value) => { // 此時p1.status 由pedding狀態 => fulfilled狀態 // console.log(value); // resolve // // console.log(p1.status); // fulfilled // p1.then(value => { // 再次p1.then 這時已經為fulfilled狀態 走的是fulfilled狀態判斷里的邏輯 所以我們也要確保判斷里面onFuilled異步執行 // console.log(value); // "resolve" // }); // console.log("當前執行棧中同步代碼"); // }) // console.log("全局執行棧中同步代碼"); // if (that.status === FULFILLED) { // 成功態 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try{ let x = onFulfilled(that.value); resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一個onFulfilled的返回值 } catch(e) { reject(e); // 捕獲前面onFulfilled中拋出的異常 then(onFulfilled, onRejected); } }); }) } if (that.status === REJECTED) { // 失敗態 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try { let x = onRejected(that.reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } if (that.status === PENDING) { // 等待態 // 當異步調用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中 return newPromise = new Promise((resolve, reject) => { that.onFulfilledCallbacks.push((value) => { try { // 回調函數 let x = onFulfilled(value); // resolve,reject都是newPromise對象下的方法 // x為返回值 resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); that.onRejectedCallbacks.push((reason) => { try { let x = onRejected(reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } };
正如之前所說的,先執行then方法注冊回調函數,然后執行resolve里面代碼(pending->fulfilled),此時執行then時候that.status為pending。
在分析that.status之前,先看下判斷onFulfilled的作用
onFulfilled =typeof onFulfilled === "function" ? onFulfilled : value => value;
如果then中并沒有回調函數的話,自定義個返回參數的函數。相當于下面這種
new Promise((resolve, reject) => { resolve("haha"); }) .then() .then(data => console.log(data)) // haha
即使第一個then沒有回調函數,但是通過自定義的回調函數,依然把最開始的數據傳遞到了最后。
回過頭我們看下then中that.pending 判斷語句,發現真的通過onRejectedCallbacks 數組注冊了回調函數。
總結下then的特點:
當status為pending時候,把then中回調函數注冊到前一個Promise對象中的onFulfilledCallbacks
返回一個新的Promise實例對象
整個resolve過程如下所示:
在上圖第5步時候,then中的回調函數就可以使用resolve中傳入的數據了。那么又把回調函數的
返回值放到resolvePromise里面干嘛,這是為了 鏈式調用。
我們看下resolvePromise函數
/** * 對resolve 進行改造增強 針對resolve中不同值情況 進行處理 * @param {promise} promise2 promise1.then方法返回的新的promise對象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { // 如果從onFulfilled中返回的x 就是promise2 就會導致循環引用報錯 return reject(new TypeError("循環引用")); } let called = false; // 避免多次調用 // 如果x是一個promise對象 (該判斷和下面 判斷是不是thenable對象重復 所以可有可無) if (x instanceof Promise) { // 獲得它的終值 繼續resolve if (x.status === PENDING) { // 如果為等待態需等待直至 x 被執行或拒絕 并解析y值 x.then(y => { resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); }); } else { // 如果 x 已經處于執行態/拒絕態(值已經被解析為普通值),用相同的值執行傳遞下去 promise x.then(resolve, reject); } // 如果 x 為對象或者函數 } else if (x != null && ((typeof x === "object") || (typeof x === "function"))) { try { // 是否是thenable對象(具有then方法的對象/函數) let then = x.then; if (typeof then === "function") { then.call(x, y => { if(called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, reason => { if(called) return; called = true; reject(reason); }) } else { // 說明是一個普通對象/函數 resolve(x); } } catch(e) { if(called) return; called = true; reject(e); } } else { // 基本類型的值(string,number等) resolve(x); } }
總結下要處理的返回值的類型:
1. Promise2本身 (暫時沒想通) 2. Promise對象實例 3. 含有then方法的函數或者對象 4. 普通值(string,number等)
自己觀察上面的代碼,我們發現不管返回值是 Promise實例,還是基本類型的值,最終都要用Promise2的resolve(返回值)來處理,然后把Promise2的resolve(返回值)中的返回值傳遞給Promise2的then中的回調函數,這樣下去就實現了鏈式操作。
如果還不懂看下面的代碼:
var obj=new Promise((resolve, reject) => { resolve({name:"李四"}); }); obj.then(data=>{ data.sex="男"; return new Promise((resolve, reject) => { resolve(data); }); }).then(data => { console.log(data); // {name: "李四", sex: "男"} });
總之,調用Promise2中的resolve方法,就會把數據傳遞給Promise2中的then中回調函數。
resolve中的值幾種情況:
1.普通值
2.promise對象
3.thenable對象/函數
如果resolve(promise對象),如何處理?
if(value instanceof Promise) { return value.then(resolve, reject); }
跟我們剛才處理的返回值是Promise實例對象一樣, 最終要把resolve里面數據轉為對象或者函數或者基本類型的值
注意:then中回調函數必須要有返回值
var obj=new Promise((resolve, reject) => { resolve({name:"張三"}); }); obj.then(data=>{ console.log(data) // {name:"李四"} // 必須要有返回值 }).then(data => { console.log(data); // undefined });
如果then中回調函數沒有返回值,那么下一個then中回調函數的值不存在。
總結一下:
then方法把then中回調函數注冊給上一個Promise對象中的onFulfilledCallbacks數組中,并交由上一個Promise對象處理。
then方法返回一個新的Promise實例對象
resolve(data)或者resolvePromise(promise2, data, resolve, reject) 中的data值
最終為基本類型或者函數或者對象中的某一種
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99507.html
摘要:從源碼看概念與實現是異步編程中的重要概念,它較好地解決了異步任務中回調嵌套的問題。這些概念中有趣的地方在于,標識狀態的變量如都是形容詞,用于傳入數據的接口如與都是動詞,而用于傳入回調函數的接口如及則在語義上用于修飾動詞的副詞。 從源碼看 Promise 概念與實現 Promise 是 JS 異步編程中的重要概念,它較好地解決了異步任務中回調嵌套的問題。在沒有引入新的語言機制的前提下,這...
摘要:源碼閱讀階段先理解根本吧想快點理解的話可以直接跳到下個標題這部分根據理解將持續修改空函數用于判斷傳入構造器的函數是否為空函數如果為空函數構造一個對象并初始化狀態為終值回調狀態和隊列記錄內部最后的一次錯誤空對象標識表示發生了錯誤暴露模塊接口為 源碼閱讀階段 先理解Promise根本吧,想快點理解的話可以直接跳到下個標題.這部分根據理解將持續修改. Promise(fn) function...
摘要:工作當中經常會用到,在此進行深入學習異步編程解決方案是異步編程的一種解決方案,比傳統的解決方案回調函數和事件更合理和更強大。所有源碼注釋見學習筆記 工作當中經常會用到Promise,在此進行深入學習 異步編程解決方案 Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:下一篇大概就是源碼方面的學習筆記了龜速學習中這一次我是去看了下規范照例傳送門圖靈社區規范首先吧個人總結下該用的詞解決結婚拒絕婉拒終值值傳家寶拒因好人卡等等異常車禍理下概念我們的的就像是一場姻緣對吧解決呢就是結婚成功啦傳家寶也如愿的傳給下一代 下一篇大概就是源碼方面的學習筆記了...龜速學習中... 這一次我是去看了下Promises/A+規范照例傳送門:圖靈社區Promises/A+規...
閱讀 4002·2023-04-26 02:13
閱讀 2244·2021-11-08 13:13
閱讀 2729·2021-10-11 10:59
閱讀 1732·2021-09-03 00:23
閱讀 1301·2019-08-30 15:53
閱讀 2275·2019-08-28 18:22
閱讀 3050·2019-08-26 10:45
閱讀 727·2019-08-23 17:58