Promise做爲一種異步處理的解決方案,以同步的寫做方式來處理異步代碼。本文只涉及Promise函數和then方法,對其餘方法(如catch,finally,race等)暫不研究。首先,看下Promise的使用場景。ajax
例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函數都是異步執行的,其餘都是同步執行。promise
經過它的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 (/^2\d{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函數的源碼源碼分析
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(失敗)。ui
that.onFulfilledCallbacks這個數組就是存儲then方法中的回調函數。執行reject函數爲何是異步的,就是由於裏面有setTimeout這個函數。當reject執行的時候,裏面的 pending狀態->fulfilled狀態,改變以後沒法再次改變狀態了。this
而後執行onFulfilledCallbacks裏面經過then註冊的回調函數。由於resolve執行的時候是異步的,因此還沒執行resolve裏面具體的代碼時候,已經經過then方法,把then中回調函數給註冊到了
onFulfilledCallbacks中,因此纔可以執行onFulfilledCallbacks裏面的回調函數。url
咱們看下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的特色:
整個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中的值幾種狀況:
若是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中回調函數的值不存在。
總結一下: