本文緣起前段時間一朋友換工作時,筆試題中要求手寫一個Promise偎行。在工作中雖已大量使用Promise猖败,其原理卻沒有深入探究過,換做自己懂扼,當場也很難手寫一個完善的Promise實現(xiàn)。近期補了一些課右蒲,以本文來徹底的理解Promise實現(xiàn)原理阀湿。
1.Promise是什么
Promise是抽象異步處理對象以及對其進行各種操作的組件,可以將復(fù)雜的異步處理輕松的進行模式化瑰妄。
使用Promise進行異步處理的一個例子
function getUserId() {
return new Promise(function (resolve, reject) {
// 異步請求
Y.io('/userid/1', {
on: {
success: function (id, res) {
var o = JSON.parse(res);
if (o.status === 1) {
resolve(o.id);
} else {
// 請求失敗陷嘴,返回錯誤信息
reject(o.errorMsg);
}
}
}
});
});
}
getUserId().then(function (id) {
// do sth with id
}, function (error) {
console.log(error);
});
如對Promise的使用尚不了解,推薦閱讀JavaScript Promise Cookbook中文版
在掌握了Promise的使用后间坐,推薦繼續(xù)閱讀Promise規(guī)范Promise A+ 規(guī)范
2.Promise實現(xiàn)原理
理解實現(xiàn)原理過程中灾挨,閱讀了不少相關(guān)文章,其中剖析 Promise 之基礎(chǔ)篇和JS Promise的實現(xiàn)原理這兩篇文章竹宋,個人認為質(zhì)量較高劳澄。本文對原理的理解,也是基于這兩篇文章逝撬。
借用MDN的圖浴骂,看看Promise的狀態(tài)遷移,每個 Promise 存在三個互斥狀態(tài):pending宪潮、fulfilled溯警、rejected,它們之間的關(guān)系是:
2.1 Promise簡易實現(xiàn)
最初我是閱讀剖析 Promise 之基礎(chǔ)篇來學(xué)習(xí)的狡相,文中初始實現(xiàn)了一個簡易版的Promise
function Promise(fn) {
var state = 'pending',
value = null,
deferreds = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
deferreds.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
deferreds.forEach(function (deferred) {
deferred(value);
});
}, 0);
}
fn(resolve);
}
function getUserId() {
return new Promise(function (resolve) {
resolve(123);
});
}
getUserId().then(function (id) {
console.log('do sth with', id);
});
這一版本的實現(xiàn)梯轻,還是很好理解的。
- Promise初始狀態(tài)為pending尽棕、value為null喳挑、延遲隊列為空。并且作為函數(shù)作用域的變量滔悉,不向外暴露
- 傳入Promise的函數(shù)fn立即執(zhí)行伊诵,將resolve傳入fn,fn執(zhí)行完成調(diào)用resolve
- resolve被定義為一個內(nèi)部函數(shù)回官,使用閉包方式來訪問value曹宴、state、deferreds歉提。遵循Promise規(guī)范笛坦,resolve方法中区转,采用異步方式執(zhí)行延遲隊列的方法
- promise對象上添加then方法,當前promise對象狀態(tài)為pending時版扩,將通過then方法注冊的新方法废离,添加到延遲隊列;當前promise對象狀態(tài)為完成時礁芦,執(zhí)行注冊的方法
2.2 串行Promise
上述簡易版本的實現(xiàn)蜻韭,相信理解起來無壓力。但是在理解剖析 Promise 之基礎(chǔ)篇文中串行Promise時宴偿,著實費了一番腦筋湘捎。
function getUserId() {
return new Promise(function (resolve) {
window.setTimeout(function () {
resolve(9876);
});
});
}
function getUserMobileById(id) {
return new Promise(function (resolve) {
console.log('start to get user mobile by id:', id);
window.setTimeout(function () {
resolve(13810001000);
});
});
}
getUserId()
.then(getUserMobileById)
.then(function (mobile) {
console.log('do sth with', mobile);
});
function Promise(fn) {
var state = 'pending',
value = null,
deferreds = [];
this.then = function (onFulfilled) {
return new Promise(function (resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
};
function handle(deferred) {
if (state === 'pending') {
deferreds.push(deferred);
return;
}
var ret = deferred.onFulfilled(value);
deferred.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve);
return;
}
}
state = 'fulfilled';
value = newValue;
setTimeout(function () {
deferreds.forEach(function (deferred) {
handle(deferred);
});
}, 0);
}
fn(resolve);
}
初看上述代碼時,跟著原文敘述窄刘,沒能徹底理解其執(zhí)行流程。通過不斷的斷點調(diào)試舷胜,才最終理解娩践。在這一過程中,明白了下面幾點
- Promise的執(zhí)行過程可以分為兩個階段烹骨,即初始時注冊階段和完成時的resolve階段
- 初始時翻伺,通過promise.then注冊的方法,保存在promise對象的延遲隊列沮焕。每次調(diào)用then方法吨岭,返回一個新promis實例,作為鏈式調(diào)用的橋接峦树,這類promise可以乘坐bridge promise
- 注冊的方法執(zhí)行完辣辫,執(zhí)行resolve時。從當前promise對象延遲隊列取出注冊的方法繼續(xù)執(zhí)行魁巩。當注冊的方法生成一個新promise實例時急灭,調(diào)用then方法注冊到對應(yīng)的延遲隊列中;否則依次resolve鏈式調(diào)用中相應(yīng)的promise實例
將上述過程表達如下
理解上述執(zhí)行流程谷遂,再為上述實現(xiàn)加上錯誤執(zhí)行過程和異常處理
function Promise(fn) {
var state = 'pending',
value = null,
deferreds = [];
this.then = function (onFulfilled, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
};
function handle(deferred) {
if (state === 'pending') {
deferreds.push(deferred);
return;
}
var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
ret;
if (cb === null) {
cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
cb(value);
return;
}
try {
ret = cb(value);
deferred.resolve(ret);
} catch (e) {
deferred.reject(e);
}
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve, reject);
return;
}
}
state = 'fulfilled';
value = newValue;
finale();
}
function reject(reason) {
state = 'rejected';
value = reason;
finale();
}
function finale() {
setTimeout(function () {
deferreds.forEach(function (deferred) {
handle(deferred);
});
}, 0);
}
fn(resolve, reject);
}
3.更好的實現(xiàn)
通過閱讀剖析 Promise 之基礎(chǔ)篇葬馋,跟進上述代碼執(zhí)行過程,相信已經(jīng)可以理解Promise實現(xiàn)原理肾扰。
在理解過程中畴嘶,讓我思考,上述執(zhí)行流程到底是哪里不易理解集晚,有沒有更好的實現(xiàn)呢窗悯?
在上述的實現(xiàn)中,關(guān)鍵方法resolve被定義在Promise中作為內(nèi)部函數(shù)甩恼,通過閉包獲取promise對象的變量的引用蟀瞧。再將回調(diào)函數(shù)和resolve方法注冊到延遲隊列沉颂,通過resolve完成了鏈式的回調(diào)。這一過程隱式調(diào)用太多悦污,不好理解铸屉。
繼續(xù)閱讀其他文章,認為這篇JS Promise的實現(xiàn)原理中有更好的實現(xiàn)切端。其完整實現(xiàn)可以查看這里彻坛。
3.1 代碼結(jié)構(gòu)
構(gòu)造函數(shù)定義如下
function Promise(resolver) {
this._status = 'pending';
this._doneCallbacks = [];
this._failCallbacks = [];
resolver(resolve, reject);
...
}
在 promise 對象中定義了成功回調(diào)和失敗回調(diào)的兩個數(shù)組,數(shù)組中的每一項是通過內(nèi)部封裝的閉包函數(shù)調(diào)用的結(jié)果踏枣,也是一個函數(shù)昌屉。這個函數(shù)可以訪問到內(nèi)部調(diào)用閉包時傳遞的 promise 對象,因此通過這種方式也可以訪問到我們需要的下一個 promise 以及其關(guān)聯(lián)的成功茵瀑、失敗回調(diào)的引用间驮。在then方法中增加閉包調(diào)用以及為前一個 promise 對象保存引用。
Promise.prototype.then = function(onResolve, onReject) {
var promise = new Promise(function() {});
this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve'));
this._failCallbacks.push(makeCallback(promise, onReject, 'reject'));
return promise;
}
then方法中調(diào)用的makeCallback即上面說到的閉包函數(shù)马昨。調(diào)用時會把 promise 對象以及相應(yīng)的回調(diào)傳遞進去竞帽,返回一個新的函數(shù)。前一個 promise 對象持有返回函數(shù)的引用鸿捧,這樣在調(diào)用返回函數(shù)時屹篓,在函數(shù)內(nèi)部就可以訪問到 promise 對象以及回調(diào)函數(shù)了。
function makeCallback(promise, callback, action) {
return function promiseCallback(value) {
...
};
}
resolve和reject函數(shù)的實現(xiàn)相對簡單
function resolve(promise, data) {
if (promise._status !== 'pending') {
return;
}
promise._status = 'fullfilled';
promise._value = data;
run(promise);
}
function reject(promise, reason) {
if (promise._status !== 'pending') {
return;
}
promise._status = 'rejected';
promise._value = reason;
run(promise);
}
function run(promise) {
// `then`方法中也會調(diào)用匙奴,所以此處仍需做一次判斷
if (promise._status === 'pending') {
return;
}
var value = promise._value;
var callbacks = promise._status === 'fullfilled'
? promise._doneCallbacks
: promise._failCallbacks;
// Promise需要異步操作
setTimeout(function () {
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i](value);
}
});
// 每個promise只能被執(zhí)行一次堆巧。雖然`_doneCallbacks`和`_failCallbacks`用戶不應(yīng)該直接訪問,
// 但還是可以訪問到泼菌,保險起見谍肤,做清空處理。
promise._doneCallbacks = [];
promise._failCallbacks = [];
}
3.2 完整實現(xiàn)
除上述基本方法外灶轰,文中還實現(xiàn)了常用靜態(tài)方法如Promise.all,Promise.race谣沸,完整代碼如下
/**
* Promise對象的內(nèi)部狀態(tài)
*
* @type {Object}
*/
var Status = {
PENDING: 'pending',
FULLFILLED: 'resolved',
REJECTED: 'rejected'
};
function empty() {}
/**
* Promise構(gòu)造函數(shù)
*
* @constructor
* @param {Function} resolver 此Promise對象管理的任務(wù)
*/
function Promise(resolver) {
// ES6原生的Promise構(gòu)造函數(shù)中,若不通過`new`調(diào)用Promise的構(gòu)造函數(shù)笋颤,會拋出TypeError異常乳附。此處與其一致
if (!(this instanceof Promise)) {
throw new TypeError('TypeError: undefined is not a promise');
}
// ES6原生的Promise構(gòu)造函數(shù)中,若無作為函數(shù)的resolver參數(shù)伴澄,會拋出TypeError異常赋除。此處與其一致
if (typeof resolver !== 'function') {
throw new TypeError('TypeError: Promise resolver undefined is not a function');
}
/**
* Promise對象內(nèi)部的狀態(tài),初始為`pending`非凌。狀態(tài)只能由`pending`到`fullfilled`或`rejected`
*
* @type {string}
*/
this._status = Status.PENDING;
/**
* Promise對象resolved/rejected后擁有的data/reason
*
* - 此處保存此值是為了當一個Promise對象被resolved或rejected后举农,繼續(xù)對其調(diào)用`then`添加任務(wù),后續(xù)處理仍能獲得當前Promise的值
*
* @type {Mixed}
*/
this._value;
/**
* 當前Promise被resolved/rejected后敞嗡,需處理的任務(wù)
*
* - 由于同一個Promise對象可以調(diào)用多次`then`方法颁糟,以添加多個并行任務(wù)航背,所以此處是一個數(shù)組
*
* @type {Array.<Function>}
*/
this._doneCallbacks = [];
this._failCallbacks = [];
var promise = this;
resolver(
function (data) {
resolve(promise, data);
},
function (reason) {
reject(promise, reason);
}
);
}
Promise.prototype = {
constructor: Promise,
/**
* Promise的`then`方法
*
* @param {Function|Mixed} onResolve 當前Promise對象被resolved后,需處理的任務(wù)
* @param {Function|Mixed} onReject 當前Promise對象被rejected后棱貌,需處理的任務(wù)
* @return {Promise} 返回一個新的Promise對象玖媚,用于鏈式操作
*/
then: function (onResolve, onReject) {
var promise = new Promise(empty);
this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve'));
this._failCallbacks.push(makeCallback(promise, onReject, 'reject'));
// 如果在一個已經(jīng)被fullfilled或rejected的promise上調(diào)用then,則需要直接執(zhí)行通過then注冊的回調(diào)函數(shù)
run(this);
return promise;
},
/**
* Promise的`done`方法
*
* @param {Function|Mixed} onResolve 當前Promise對象被resolved后婚脱,需處理的任務(wù)
* @return {Promise} 返回一個新的Promise對象今魔,用于鏈式操作
*/
done: function (onResolve) {
return this.then(onResolve, null);
},
/**
* Promise的`fail`方法
*
* @param {Function|Mixed} onReject 當前Promise對象被rejected后,需處理的任務(wù)
* @return {Promise} 返回一個新的Promise對象障贸,用于鏈式操作
*/
fail: function (onReject) {
return this.then(null, onReject);
},
/**
* Promise的`catch`方法
*
* @param {Function|Mixed} onFail 當前Promise對象被rejected后错森,需處理的任務(wù)
* @return {Promise} 返回一個新的Promise對象,用于鏈式操作
*/
catch: function (onFail) {
return this.then(null, onFail);
}
};
/**
* 創(chuàng)建一個Promise對象篮洁,并用給定值resolve它
*
* @param {Mixed} value 用于resolve新創(chuàng)建的Promise對象的值
* @return {Promise} 返回一個新的Promise對象涩维,用于鏈式操作
*/
Promise.resolve = function (value) {
var promise = new Promise(empty);
resolve(promise, value);
return promise;
};
/**
* 創(chuàng)建一個Promise對象,并用給定值reject它
*
* @param {Mixed} reason 用于reject新創(chuàng)建的Promise對象的值
* @return {Promise} 返回一個新的Promise對象嘀粱,用于鏈式操作
*/
Promise.reject = function (reason) {
var promise = new Promise(empty);
reject(promise, reason);
return promise;
};
/**
* 返回一個promise激挪,這個promise在iterable中的任意一個promise被解決或拒絕后,
* 立刻以相同的解決值被解決或以相同的拒絕原因被拒絕
*
* @param {Iterable.<Promise|Mixed>} iterable 一組Promise對象或其它值
* @return {Promise} 返回一個新的Promise對象锋叨,用于鏈式操作
*/
Promise.race = function (iterable) {
if (!iterable || !iterable.hasOwnProperty('length')) {
throw new TypeError('TypeError: Parameter `iterable` must be a iterable object');
}
var promise = new Promise(empty);
for (var i = 0, len = iterable.length; i < len; i++) {
var iterate = iterable[i];
if (!(iterate instanceof Promise)) {
iterate = Promise.resolve(iterate);
}
iterate.then(resolveRaceCallback, rejectRaceCallback);
}
var settled = false;
function resolveRaceCallback(data) {
if (settled) {
return;
}
settled = true;
resolve(promise, data);
}
function rejectRaceCallback(reason) {
if (settled) {
return;
}
settled = true;
reject(promise, reason);
}
};
/**
* 返回一個promise,該promise會在iterable參數(shù)內(nèi)的所有promise都被解決后被解決
*
* @param {Iterable.<Promise|Mixed>} iterable 一組Promise對象或其它值
* @return {Promise} 返回一個新的Promise對象宛篇,用于鏈式操作
*/
Promise.all = function (iterable) {
if (!iterable || !iterable.hasOwnProperty('length')) {
throw new TypeError('TypeError: Parameter `iterable` must be a iterable object');
}
var promise = new Promise(empty);
var length = iterable.length;
for (var i = 0; i < length; i++) {
var iterate = iterable[i];
if (!(iterate instanceof Promise)) {
iterate = Promise.resolve(iterate);
}
iterate.then(makeAllCallback(iterate, i, 'resolve'), makeAllCallback(iterate, i, 'reject'));
}
var result = [];
var count = 0;
function makeAllCallback(iterate, index, action) {
return function (value) {
if (action === 'reject') {
reject(promise, value);
return;
}
result[index] = value;
if (++count === length) {
resolve(promise, result);
}
}
}
};
/**
* 返回一個Deferred對象娃磺,包含一個新創(chuàng)建的Promise對象,以及`resolve`和`reject`方法
*
* @return {Deferred}
*/
Promise.defer = function () {
var promise = new Promise(empty);
return {
promise: promise,
resolve: function (data) {
resolve(promise, data);
},
reject: function (reason) {
reject(promise, reason);
}
};
};
function run(promise) {
// `then`方法中也會調(diào)用叫倍,所以此處仍需做一次判斷
if (promise._status === Status.PENDING) {
return;
}
var value = promise._value;
var callbacks = promise._status === Status.FULLFILLED
? promise._doneCallbacks
: promise._failCallbacks;
// Promise需要異步操作
setTimeout(function () {
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i](value);
}
});
// 每個promise只能被執(zhí)行一次偷卧。雖然`_doneCallbacks`和`_failCallbacks`用戶不應(yīng)該直接訪問,
// 但還是可以訪問到吆倦,保險起見听诸,做清空處理。
promise._doneCallbacks = [];
promise._failCallbacks = [];
}
function resolve(promise, data) {
if (promise._status !== Status.PENDING) {
return;
}
promise._status = Status.FULLFILLED;
promise._value = data;
run(promise);
}
function reject(promise, reason) {
if (promise._status !== Status.PENDING) {
return;
}
promise._status = Status.REJECTED;
promise._value = reason;
run(promise);
}
function makeCallback(promise, callback, action) {
return function promiseCallback(value) {
// 如果傳遞了callback蚕泽,則使用前一個promise傳遞過來的值作為參數(shù)調(diào)用callback晌梨,
// 并根據(jù)callback的調(diào)用結(jié)果來處理當前promise
if (typeof callback === 'function') {
var x;
try {
x = callback(value);
}
catch (e) {
// 如果調(diào)用callback時拋出異常,則直接用此異常對象reject當前promise
reject(promise, e);
}
// 如果callback的返回值是當前promise须妻,為避免造成死循環(huán)仔蝌,需要拋出異常
// 根據(jù)Promise+規(guī)范,此處應(yīng)拋出TypeError異常
if (x === promise) {
var reason = new TypeError('TypeError: The return value could not be same with the promise');
reject(promise, reason);
}
// 如果返回值是一個Promise對象荒吏,則當返回的Promise對象被resolve/reject后敛惊,再resolve/reject當前Promise
else if (x instanceof Promise) {
x.then(
function (data) {
resolve(promise, data);
},
function (reason) {
reject(promise, reason);
}
);
}
else {
var then;
(function resolveThenable(x) {
// 如果返回的是一個Thenable對象(此處邏輯有點坑,參照Promise+的規(guī)范實現(xiàn))
if (x && (typeof x === 'object'|| typeof x === 'function')) {
try {
then = x.then;
}
catch (e) {
reject(promise, e);
return;
}
if (typeof then === 'function') {
// 調(diào)用Thenable對象的`then`方法時绰更,傳遞進去的`resolvePromise`和`rejectPromise`方法(及下面的兩個匿名方法)
// 可能會被重復(fù)調(diào)用瞧挤。但Promise+規(guī)范規(guī)定這兩個方法有且只能有其中的一個被調(diào)用一次锡宋,多次調(diào)用將被忽略。
// 此處通過`invoked`來處理重復(fù)調(diào)用
var invoked = false;
try {
then.call(
x,
function (y) {
if (invoked) {
return;
}
invoked = true;
// 避免死循環(huán)
if (y === x) {
throw new TypeError('TypeError: The return value could not be same with the previous thenable object');
}
// y仍有可能是thenable對象特恬,遞歸調(diào)用
resolveThenable(y);
},
function (e) {
if (invoked) {
return;
}
invoked = true;
reject(promise, e);
}
);
}
catch (e) {
// 如果`resolvePromise`和`rejectPromise`方法被調(diào)用后执俩,再拋出異常,則忽略異常
// 否則用異常對象reject此Promise對象
if (!invoked) {
reject(promise, e);
}
}
}
else {
resolve(promise, x);
}
}
else {
resolve(promise, x);
}
}(x));
}
}
// 如果未傳遞callback鸵鸥,直接用前一個promise傳遞過來的值resolve/reject當前Promise對象
else {
action === 'resolve'
? resolve(promise, value)
: reject(promise, value);
}
};
}
4.最后
相比奠滑,剖析 Promise 之基礎(chǔ)篇,JS Promise的實現(xiàn)原理文中代碼結(jié)構(gòu)要更加清晰妒穴,對閉包的調(diào)用更為易讀宋税。
像resolve(),reject(),run(),makeCallback()這幾個工具方法,沒有直接定義到Promise構(gòu)造函數(shù)中讼油,作為內(nèi)部函數(shù)杰赛,產(chǎn)生隱式的閉包調(diào)用,而是提出來作為公共方法矮台,通過參數(shù)傳遞方式將promise對象傳入方法乏屯。使得代碼可讀性好,容易理解瘦赫。
同時辰晕,在學(xué)習(xí)的過程中,讀到這篇奇舞團翻譯的Bluebird 是如何做到比原生實現(xiàn)更快的确虱?
文中提到了Bluebird相對于原生Promise的幾個優(yōu)化點
- 函數(shù)中的對象分配最小化
- 減小對象體積
- 可選特性懶重寫
其第一點就是避免在Promise構(gòu)造函數(shù)中含友,直接定義其他內(nèi)部函數(shù)。除開前面提到的可讀性問題校辩,避免這樣做窘问,可以避免每次創(chuàng)建Promise對象時,同時創(chuàng)建全新的內(nèi)部函數(shù)對象宜咒。
參考文章
- JavaScript Promise Cookbook中文版
- Promise A+ 規(guī)范
- 剖析 Promise 之基礎(chǔ)篇
- JS Promise的實現(xiàn)原理
- Bluebird 是如何做到比原生實現(xiàn)更快的惠赫?
- 剖析Promise內(nèi)部結(jié)構(gòu),一步一步實現(xiàn)一個完整的故黑、能通過所有Test case的Promise類
- 細嗅promise
如果覺得有幫助儿咱,可以掃描二維碼對我打賞,謝謝