開篇
最近在 github 上看到了一個 extremely lightweight Promise polyfill 完成,翻開源碼發明只需240行,果真極為輕量級,因而帶着讚歎和獵奇的心思去了解了下其細緻完成。
源碼的 github 地點:promise-polyfill
Promise 關於前端來講,是個陳詞濫調的話題,Promise 的湧現處置懲罰了 js 回調地區的題目。現在市情上有許多 Promise 庫,但其終究完成都要順從 Promise/A+ 範例,這裏對範例不做解讀,有興緻的能夠檢察鏈接內容。
Promise/A+範例鏈接
Promise/A+範例中文鏈接
本篇文章將從 Promise 的運用角度來理會源碼細緻完成。
API 列表
Promise // 組織函數
Promise.prototype.then
Promise.prototype.catch
Promise.prototype.finally
// 靜態要領
Promise.resolve
Promise.reject
Promise.race
Promise.all
源碼剖析
組織函數
運用
Promise 運用第一步,組織實例,傳入 Function 形參,形參吸收兩個 Function 範例參數resolve, reject
const asyncTask = () => {};
const pro = new Promise((resolve, reject) => {
asyncTask((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
源碼
function Promise(fn) {
if (!(this instanceof Promise))
throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function') throw new TypeError('not a function');
this._state = 0;
this._handled = false;
this._value = undefined;
this._deferreds = [];
doResolve(fn, this);
}
function doResolve(fn, self) {
// done變量庇護 resolve 和 reject 只實行一次
// 這個done在 Promise.race()函數中有效
var done = false;
try {
// 馬上實行 Promise 傳入的 fn(resolve,reject)
fn(
function(value) {
// resolve 回調
if (done) return;
done = true;
resolve(self, value);
},
function(reason) {
// reject 回調
if (done) return;
done = true;
reject(self, reason);
}
);
} catch (ex) {
if (done) return;
done = true;
reject(self, ex);
}
}
Promise必需經由過程組織函數實例化來運用,傳入 Promise 組織函數的形參 fn 在doResolve要領內是 馬上挪用實行 的,並沒有異步(指放入事宜輪迴行列)處置懲罰。doResolve內部針對 fn 函數的回調參數做了封裝處置懲罰,done變量保證了 resolve reject 要領只實行一次,這在背面說到的Promise.race()函數完成有很大用途。
Promise 實例的內部變量引見
稱號 | 範例 | 默認值 | 形貌 |
---|---|---|---|
_state | Number | 0 | Promise內部狀況碼 |
_handled | Boolean | false | onFulfilled,onRejected是不是被處置懲罰過 |
_value | Any | undefined | Promise 內部值,resolve 或許 reject返回的值 |
_deferreds | Array | [] | 寄存 Handle 實例對象的數組,緩存 then 要領傳入的回調 |
_state羅列值範例
_state === 0 // pending
_state === 1 // fulfilled,實行了resolve函數,而且_value instanceof Promise === true
_state === 2 // rejected,實行了reject函數
_state === 3 // fulfilled,實行了resolve函數,而且_value instanceof Promise === false
注重:這裏_state區分了1 和 3 兩種狀況,下面會詮釋緣由
/**
* Handle 組織函數
* @param onFulfilled resolve 回調函數
* @param onRejected reject 回調函數
* @param promise 下一個 promise 實例對象
* @constructor
*/
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
_deferreds數組的意義:當在 Promise 內部挪用了異步處置懲罰使命時,pro.then(onFulfilled,onRejected)傳入的兩個函數不會馬上實行,所以此時會把當前的回折衷下一個 pro 對象關聯緩存起來,待到 resolve 或許 reject觸發挪用時,會去 forEach 這個_deferreds數組中的每一個 Handle 實例去處置懲罰對應的 onFulfilled,onRejected 要領。
Promise 內部 resolve reject finale 要領
上面說到,doResolve 內部做了 fn 的馬上實行,並保證 resolve 和 reject 要領只實行一次,接下來講說resolve 和 reject 內部細緻做了什麼
function resolve(self, newValue) {
try {
// resolve 的值不能為本身 this 對象
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self)
throw new TypeError('A promise cannot be resolved with itself.');
// 針對 resolve 值為 Promise 對象的狀況處置懲罰
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
var then = newValue.then;
if (newValue instanceof Promise) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
// 兼容類 Promise 對象的處置懲罰體式格局,對其 then 要領繼承實行 doResolve
doResolve(bind(then, newValue), self);
return;
}
}
// resolve 一般值的流程,_state = 1
self._state = 1;
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
finale(self);
}
function finale(self) {
// Promise reject 狀況,然則 then 要領未供應 reject 回調函數參數 或許 未完成 catch 函數
if (self._state === 2 && self._deferreds.length === 0) {
Promise._immediateFn(function() {
if (!self._handled) {
Promise._unhandledRejectionFn(self._value);
}
});
}
for (var i = 0, len = self._deferreds.length; i < len; i++) {
// 這裏挪用之前 then 要領傳入的onFulfilled, onRejected函數
// self._deferreds[i] => Handler 實例對象
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
resolve,reject 是由用戶在異步使命內里觸發的回調函數
挪用 resolve reject 要領的注重點
1、newValue不能為當前的 this 對象,即下面的如許寫法是毛病的
const pro = new Promise((resolve)=>{setTimeout(function () {
resolve(pro);
},1000)});
pro.then(data => console.log(data)).catch(err => {console.log(err)});
由於resolve做了 try catch 的操縱,直接會進入 reject 流程。
2、newValue能夠為另一個Promise 對象範例實例, resolve 的值返回的是另一個 Promise 對象實例的內部的_value,而不是其本身 Promise 對象。即能夠如許寫
const pro1 = new Promise((resolve)=>{setTimeout(function () {
resolve(100);
},2000)});
const pro = new Promise((resolve)=>{setTimeout(function () {
resolve(pro1);
},1000)});
pro.then(data => console.log('resolve' + data)).catch(err => {console.log('reject' + err)});
// 輸出效果:resolve 100
// data 並非pro1對象
細緻緣由就在 resolve 要領體內部做了newValue instanceof Promise的推斷,並將當前的_state=3,self._value = newValue,然後進入 finale 要領體,在 handle 要領做了中心處置懲罰,這個下面引見 handle 要領會說到;
這裡有一個注重點,resolve 的 value 多是其他框架的 Promise(比方:global.Promise,nodejs 內部的 Promise 完成) 組織實例,所以在typeof then === ‘function’條件下做了doResolve(bind(then, newValue), self);的從新挪用,繼承實行當前範例的 Promise then 要領,即又從新回到了doResolve流程。
假如這裏的完成體式格局輕微調解下,即不論newValue是本身的 Promise 實例照樣其他框架完成的 Promise實例,都實行doResolve(bind(then, newValue), self)也能行得通,只不過會多實行 then 體式格局一次,從代碼機能上說,上面的完成體式格局會更好。參照代碼以下
function resolve(self, newValue) {
try {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self)
throw new TypeError('A promise cannot be resolved with itself.');
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
// 這裏簡樸粗獷處置懲罰,無論是 Promise 照樣 global.Promise
// 都直接挪用doResolve
var then = newValue.then;
if (typeof then === 'function') {
doResolve(bind(then, newValue), self);
return;
}
}
// resolve 一般值的流程,_state = 1
self._state = 1;
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
一切 resolve 和 reject 的值終究都邑去到finale函數中去處置懲罰,只不過在這裏的_state狀況會有所不同;當Promise 湧現reject的狀況時,而沒有供應 onRejected 函數時,內部會打印一個毛病出來,提醒要捕捉毛病。代碼完成即
const pro = new Promise((resolve,reject)=>{setTimeout(function () {
reject(100);
},1000)});
pro.then(data => console.log(data)); // 會報錯
pro.then(data => console.log(data)).catch(); // 會報錯
pro.then(data => console.log(data)).catch(()=>{}); // 不會報錯
pro.then(data => console.log(data),()=>{}) // 不會報錯
then、catch、finally 要領
第二步,挪用 then 要領來處置懲罰回調,支撐無窮鏈式挪用,then 要領第一個參數勝利回調,第二個參數失利或許非常回調
源碼
function noop() {}
Promise.prototype.then = function(onFulfilled, onRejected) {
var prom = new this.constructor(noop);
handle(this, new Handler(onFulfilled, onRejected, prom));
return prom;
};
Promise.prototype['catch'] = function(onRejected) {
return this.then(null, onRejected);
};
Promise.prototype['finally'] = function(callback) {
var constructor = this.constructor;
return this.then(
function(value) {
return constructor.resolve(callback()).then(function() {
return value;
});
},
function(reason) {
return constructor.resolve(callback()).then(function() {
return constructor.reject(reason);
});
}
);
};
Promise.prototype.then要領內部組織了一個新的Promsie 實例並返回,如許從 api 角度處置懲罰了 Promise 鏈式挪用的題目,而且值得注重的是,每一個 then 要領返回的都是一個新的 Promise 對象,並非當前的 this鏈接挪用體式格局。終究的處置懲罰都邑挪用 handle 要領。
catch要領在 then 要領上做了一個簡樸的封裝,所以從這裏也能夠看出,then 要領的形參並非必傳的,catch 只吸收onRejected。
finally要領不論是挪用了 then 照樣 catch,終究都邑實行到finally的 callback
中心邏輯:handle要領內部完成
上面說了這麼多,終究的 resolve reject 回調解置懲罰都邑進入到 handle 要領中,來處置懲罰onFulfilled 和 onRejected,先看源碼
Promise._immediateFn =
(typeof setImmediate === 'function' &&
function(fn) {
setImmediate(fn);
}) ||
function(fn) {
setTimeoutFunc(fn, 0);
};
function handle(self, deferred) {
// 假如當前的self._value instanceof Promise
// 將self._value => self,接下來處置懲罰新 Promise
while (self._state === 3) {
self = self._value;
}
// self._state=== 0 申明還沒有實行 resolve || reject 要領
// 此處將 handle 掛起
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
self._handled = true;
// 經由過程事宜輪迴異步來做回調的處置懲罰
Promise._immediateFn(function() {
// deferred.promise :第一個 Promise then 要領 返回的新 Promise 對象
// 這裏挪用下一個 Promise 對象的 then 要領的回調函數
// 假如當前 Promise resolve 了,則挪用下一個 Promise 的 resolve要領,反之,則挪用下一個 Promise 的 reject 回調
// 假如當前 Promise resolve 了,則挪用下一個 Promise 的 resolve要領
// cb回調要領:假如本身有onFulfilled||onRejected要領,則實行本身的要領;假如沒有,則挪用下一個 Promise 對象的onFulfilled||onRejected
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
// 本身沒有回調函數,進入下一個 Promise 對象的回調
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
// 本身有回調函數,進入本身的回調函數
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
// 處置懲罰下一個 Promise 的 then 回調要領
// ret 作為上一個Promise then 回調 return的值 => 返回給下一個Promise then 作為輸入值
resolve(deferred.promise, ret);
});
}
self._state === 3,申明當前 resolve(promise)要領回傳的值範例為 Promise 對象,
即 self._value instanceOf Promise === true, 將 self=self._value,即當前處置懲罰變動到了新的 Promise 對象上 ,假如當前 promise對象內部狀況是fulfilled或許 rejected,則直接處置懲罰onFulfilled 或許 onRejected回調;假如依然是 padding 狀況,則繼承守候。這就很好的詮釋了為何resolve(pro1),pro.then的回調取的值倒是 pro1._value.
從運用角度來看
const pro1 = new Promise(resolve=>{setTimeout(()=>{resolve(100)},1000)}) // 實行耗時1s 的異步使命
pro.then(()=>pro1).then(data => console.log(data)).catch(err => {});
// 輸出效果: 一般打印了100,data並非當前的pro1對象
pro1內部是耗時1s 的異步使命,此時self._state === 0,即內部是 Padding 狀況,則將deferred對象 push 到_deferreds數組內里,然後守候 pro1內部挪用resolve(100)時,繼承上面resolve要領體實行
const pro1 = new Promise(resolve=>resolve(100)}) // 實行同步使命
pro.then(()=>pro1).then(data => console.log(data)).catch(err => {});
// 輸出效果: 一般打印了100,data並非當前的pro1對象
然則假如pro1內部是同步使命,馬上實行的話,當前的self._state === 1,即調過 push 到_deferreds數組的操縱,實行末了的onFulfilled, onRejected回調,onFulfilled, onRejected會被放入到事宜輪迴行列內里實行,即實行到了Promise._immediateFn
Promise._immediateFn回調函數放到了事宜輪迴行列內里來實行
這裏的deferred對象寄存了當前的onFulfilled和onRejected回調函數和下一個 promise 對象。
當前對象的onFulfilled和onRejected假如存在時,則實行本身的回調;
pro.then(data => data}).then(data => data).catch(err => {});
// 準確寫法: 輸出兩次 data
注重:then 要領肯定要做 return 下一個值的操縱,由於當前的 ret 值會被帶入到下一個 Promise 對象,即 resolve(deferred.promise, ret)。假如不供應返回值,則第二個 then 的 data 會變成 undefined,即如許的毛病寫法
pro.then(data => {}}).then(data => data).catch(err => {});
// 毛病寫法: 第二個 then 要領的 data 為 undefined
假如onFulfilled和onRejected回調不存在,則實行下一個 promise 的回調並照顧當前的_value 值。即能夠如許寫
pro.then().then().then().then(data => {}).catch(err => {});
// 準確寫法: 第四個 then 要領依然能取到第一個pro 的內部_value 值
// 固然前面的三個 then 寫起來毫無用途
所以針對下面的狀況:當第一個 then 供應了 reject 回調,背面又跟了個 catch 要領。
當 reject 時,會優先實行第一個 Promise 的onRejected回調函數,catch 是鄙人一個 Promise 對象上的捕捉毛病要領
pro.then(data => data,err => err).catch(err => err);
終究總結:resolve 要麼供應帶返回值的回調,要麼不供應回調函數
靜態要領:race
Promise.race = function(values) {
return new Promise(function(resolve, reject) {
for (var i = 0, len = values.length; i < len; i++) {
// 由於doResolve要領內部 done 變量掌握了對 resolve reject 要領只實行一次的處置懲罰
// 所以這裏完成很簡樸,清楚清楚明了,最快的 Promise 實行了 resolve||reject,背面相對慢的 // Promise都不實行
values[i].then(resolve, reject);
}
});
};
用法
Promise.race([pro1,pro2,pro3]).then()
race的完成非常奇妙,對當前的 values(必需是 Promise 數組) for 輪迴實行每一個 Promise 的 then 要領,resolve, reject要領關於一切race中 promise 對象都是公用的,從而應用doResolve內部的 done變量,保證了最快實行的 Promise 能做 resolve reject 的回調,從而達到了多個Promise race 比賽的機制,誰跑的快實行誰。
靜態要領:all
Promise.all = function(arr) {
return new Promise(function(resolve, reject) {
if (!arr || typeof arr.length === 'undefined')
throw new TypeError('Promise.all accepts an array');
var args = Array.prototype.slice.call(arr);
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
// 假如 val 是 Promise 對象的話,則實行 Promise,直到 resolve 了一個非 Promise 對象
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(
val,
function(val) {
res(i, val);
},
reject
);
return;
}
}
// 用當前resolve||reject 的值重寫 args[i]{Promise} 對象
args[i] = val;
// 直到一切的 Promise 都實行終了,則 resolve all 的 Promise 對象,返回args數組效果
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
// 只需个中一個 Promise 湧現非常,則悉數的 Promise 實行退出,進入 catch非常處置懲罰
// 由於 resolve 和 reject 回調有 done 變量的保證只能實行一次,所以其他的 Promise 都不實行
reject(ex);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
用法
Promise.all([pro1,pro2,pro3]).then()
all 守候一切的 Promise 都實行終了,才會實行 Promise.all().then()回調,只需个中一個失足,則直接進入毛病回調,由於關於一切 all 中 promise 對象 reject 回調是公用的,應用doResolve內部的 done變量,保證一次毛病停止一切操縱。
然則關於 resolve 則不一樣, resolve 回調函數經由過程 res 遞歸挪用本身,從而保證其值_value不為 Promise 範例才完畢,並將_value 賦值到 args 數組,末了直到一切的數組Promise都處置懲罰終了由一致的 resolve 要領完畢當前的 all 操縱,進入 then 處置懲罰流程。
完畢語
本篇針對 Promise 的一切 api 做了細緻的代碼詮釋和運用場景,篇幅能夠太長,看起來比較辛苦,假如有寫的不對的處所迎接斧正。
末了附上我的 github 源碼解釋版鏈接 promise源碼解釋版