學習自阮一峯老師 ECMAScript 6 入門 Promise介紹
參考 promise-實現 es6
Promise屬於微任務,而模擬的 Promise 都是使用setTimeout,屬於宏任務。因此在某些狀況下會有bug,須要注意,如:ajax
setTimeout('console.log(1)') new Promise(resolve=>{ console.log(2) resolve() }).then(_=>{ console.log(3) })
正確的結果應該是 2 3 1,可是模擬的 Promise 返回 1 2 3。
bluebird,es6-promise等也有這個問題。數組
// 三種狀態 + 1 var PENDING = "pending"; var RESOLVED = "resolved"; var REJECTED = "rejected"; var FINALLY = "finally"; var Promise = window.Promise || function(){ var Promise = function(fn){ var that = this; that.currentState = PENDING; that.value = undefined; // 用於保存 then 中的回調,只有當 promise // 狀態爲 pending 時纔會緩存,而且每一個實例至多緩存一個 that.Callbacks = [] //這個函數是封裝的resolve,reject的公共方法,這個方法不執行,則then、catch、finally中的回調不執行 var stateF = function(state, value){ setTimeout(function(){ // 異步執行,保證執行順序 if (that.currentState === PENDING) { that.value = value; var cb; //回調狀態,默認爲Promise狀態 //經過修改回調狀態,來肯定運行哪一個回調 //如沒有拋出錯誤,則不運行 REJECTED 回調;拋出錯誤,則catch以前的 RESOLVED 回調不運行 var cbState = that.currentState = state; while(cb = that.Callbacks.shift()){ //狀態爲 RESOLVED,運行then回調;狀態爲 REJECTED,運行catch回調 if(cb.state == cbState || cb.state == FINALLY){ try{ if(cb.state == FINALLY){ //狀態爲 finally 的回調不接受參數,不返回參數 cb.fn() }else{ //運行正確,回調狀態變爲 RESOLVED cbState = RESOLVED //該回調的返回值是下一個 then 回調的參數 that.value = cb.fn(that.value) } }catch(err){ //運行錯誤,回調狀態變爲 REJECTED cbState = REJECTED //該錯誤是下一個 catch 回調的參數 that.value = err } } } } }) } var resolve = function(value){ if (value instanceof Promise) { // 若是 value 是個 Promise,遞歸執行 //等 Promise 狀態返回後,從新運行 resolve 或 reject return value.then(resolve, reject) } stateF(RESOLVED, value) } var reject = function (reason) { stateF(REJECTED, reason) } // new Promise(() => throw Error('error)) try { fn(resolve, reject); } catch (e) { reject(e); } } /* Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。 const p = Promise.all([p1, p2, p3]); 上面代碼中,Promise.all方法接受一個數組做爲參數,p一、p二、p3都是 Promise 實例,若是不是,就會先調用Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。 只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。 只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。 Promise.all方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。 * */ Promise.all = function(arr){ arr = Array.prototype.slice.apply(arr) var values = [], count = 0; var p = new Promise(function(resolve, reject){ arr.forEach(function(item, index){ Promise.resolve(item).then(function(value){ values[ index ] = value; count++ if(p.currentState === PENDING && count === arr.length){ resolve(values) } }).catch(function(e){ p.currentState === PENDING && reject(e) }) }) }) return p } /* Promise.race方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。 const p = Promise.race([p1, p2, p3]); 只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。 * */ Promise.race = function(arr){ arr = Array.prototype.slice.apply(arr) var p = new Promise(function(resolve, reject){ arr.forEach(function(item, index){ Promise.resolve(item).then(function(value){ p.currentState === PENDING && resolve(value) }).catch(function(e){ p.currentState === PENDING && reject(e) }) }) }) return p } /* 將現有對象轉爲 Promise 對象 若是參數是 Promise 實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。 參數是一個具備then方法的對象,Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。 若是參數是一個原始值,或者是一個不具備then方法的對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。 Promise.resolve方法容許調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。 * */ Promise.resolve = function(value){ if (value instanceof Promise) { return value } var fn = value.toString() === "[object Object]" && typeof value.then === 'function' ? value.then : function(resolve){ resolve(value) }; return new Promise(fn) } /* Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。 Promise.reject()方法的參數,會原封不動地做爲reject的參數,變成後續方法的參數。這一點與Promise.resolve方法不一致 * */ Promise.reject = function(value){ return new Promise(function(resolve, reject){ reject(value) }) } //提取的公共方法,then,catch,finally共用 function bindF(state, fn){ if(typeof fn === 'function'){ if(this.currentState === PENDING){ this.Callbacks.push({ state: state, fn: fn }) }else{ //狀態改變後,不是REJECTED,直接運行 state !== REJECTED && fn(this.value) } } return this } /* then方法的做用是爲 Promise 實例添加狀態改變時的回調函數。 then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。 * */ Promise.prototype.then = function(resolve, reject){ var rs, rj; var p = new Promise(function(_resolve, _reject){ rs = function(value){ _resolve( typeof resolve === 'function' ? resolve(value) : value ) } rj = function(value){ _reject( typeof reject === 'function' ? reject(value) : value ) } }) bindF.call(this, RESOLVED, rs) bindF.call(this, REJECTED, rj) return p } /* Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名 用於指定發生錯誤時的回調函數。 * */ Promise.prototype.catch = function(reject){ return bindF.call(this, REJECTED, reject) } /* finally方法用於指定無論 Promise 對象狀態是 resolve 或 reject ,都會執行的操做。 * */ Promise.prototype.finally = function(fina){ return bindF.call(this, FINALLY, fina) } return Promise }() if (typeof Promise.try !== 'function') { Promise.try = function(fn){ return new Promise(function (resolve) { resolve(fn()); }); } }
實際開發中,常常遇到一種狀況:不知道或者不想區分,函數f是同步函數仍是異步操做,可是想用 Promise 來處理它。
通常的,爲了確保同步函數同步執行,異步函數異步執行,咱們會這麼作:promise
const f1 = () => new Promise(resolve=>{ setTimeout(_=>{ resolve([1,2,3]) },1000) }); const f2 = _ => ({a: 1}); new Promise( resolve => resolve(f1()) ).then(data=>{console.log(data)}) new Promise( resolve => resolve(f2()) ).then(data=>{console.log(data)}) //{a: 1} //[1, 2, 3]
同時,這麼寫還能夠更好的處理錯誤。緩存
若是同步或者異步方法自己是錯誤的,咱們需在外層套一層try來處理,這種寫法則能夠省略try,使代碼更美觀。app
針對這種寫法,如今已經有提案,提供Promise.try方法替代上面的寫法。frontend
使用案例:異步
Promise.try(_=>{ myajax() }).then(data=>{ console.log(data) })