十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
if (typeof Promise === 'undefined') {
return}實(shí)現(xiàn) Promise/A+ 規(guī)范的庫(kù)有很多,lie是一個(gè)精簡(jiǎn)的實(shí)現(xiàn) Promise/A+ 的庫(kù),并且通過了 Promise/A+ 專門的測(cè)試集,但 lie 的代碼寫的有點(diǎn)繞,我在 lie 的代碼基礎(chǔ)上進(jìn)行了修改,使之更容易閱讀和理解,并發(fā)布了 appoint模塊供大家參考。

Promise 規(guī)范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升級(jí)版 Promise/A+,有興趣的可以去了解下,最終 ES6 中采用了 Promise/A+ 規(guī)范。在講解 Promise 實(shí)現(xiàn)之前,當(dāng)然要先了解 Promise/A+ 規(guī)范。Promise/A+ 規(guī)范參考:
英文版:https://promisesaplus.com/
中文版:http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/
注意:沒有特殊說明以下 promise 均指代 Promise 實(shí)例。
規(guī)范雖然不長(zhǎng),但細(xì)節(jié)也比較多,我挑出幾個(gè)要點(diǎn)簡(jiǎn)單說明下:
Promise 本質(zhì)是一個(gè)狀態(tài)機(jī)。每個(gè) promise 只能是 3 種狀態(tài)中的一種:pending、fulfilled 或 rejected。狀態(tài)轉(zhuǎn)變只能是 pending -> fulfilled 或者 pending -> rejected。狀態(tài)轉(zhuǎn)變不可逆。
then 方法可以被同一個(gè) promise 調(diào)用多次。
then 方法必須返回一個(gè) promise。規(guī)范里沒有明確說明返回一個(gè)新的 promise 還是復(fù)用老的 promise(即 return this),大多數(shù)實(shí)現(xiàn)都是返回一個(gè)新的 promise,而且復(fù)用老的 promise 可能改變內(nèi)部狀態(tài),這與規(guī)范也是相違背的。
值穿透。下面會(huì)細(xì)講。
我們知道 Promise 是一個(gè)構(gòu)造函數(shù),需要用 new 調(diào)用,并有以下幾個(gè) api:
function Promise(resolver) {}Promise.prototype.then = function() {}Promise.prototype.catch = function() {}Promise.resolve = function() {}Promise.reject = function() {}Promise.all = function() {}Promise.race = function() {}下面我們以 appoint 為最終目標(biāo),開始一步一步構(gòu)建完整的 Promise 實(shí)現(xiàn)。
'use strict';var immediate = require('immediate');function INTERNAL() {}function isFunction(func) {
return typeof func === 'function';}function isObject(obj) {
return typeof obj === 'object';}function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';}var PENDING = 0;var FULFILLED = 1;var REJECTED = 2;module.exports = Promise;function Promise(resolver) {
if (!isFunction(resolver)) {
throw new TypeError('resolver must be a function');
}
this.state = PENDING;
this.value = void 0;
this.queue = [];
if (resolver !== INTERNAL) {
safelyResolveThen(this, resolver);
}}immediate 是一個(gè)將同步轉(zhuǎn)異步執(zhí)行的庫(kù)。INTERNAL 就是一個(gè)空函數(shù),類似于一些代碼庫(kù)中的 noop。定義了 3 個(gè)輔助函數(shù):isFunction、isObject 和 isArray。定義了 3 種狀態(tài):PENDING、FULFILLED 和 REJECTED。safelyResolveThen 后面講。promise 內(nèi)部有三個(gè)變量:
state: 當(dāng)前 promise 的狀態(tài),初始值為 PENDING。狀態(tài)改變只能是 PENDING -> FULFILLED 或 PENDING -> REJECTED。
value: 當(dāng) state 是 FULFILLED 時(shí)存儲(chǔ)返回值,當(dāng) state 是 REJECTED 時(shí)存儲(chǔ)錯(cuò)誤。
queue: promise 內(nèi)部的回調(diào)隊(duì)列,這是個(gè)什么玩意兒?為什么是一個(gè)數(shù)組?
先看一段代碼:
var Promise = require('appoint')var promise = new Promise((resolve) => {
setTimeout(() => {
resolve('haha')
}, 1000)})var a = promise.then(function onSuccess() {})var b = promise.catch(function onError() {})console.log(require('util').inspect(promise, { depth: 10 }))console.log(promise.queue[0].promise === a)console.log(promise.queue[1].promise === b)打印出:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise: Promise { state: 0, value: undefined, queue: [] },
callFulfilled: [Function],
callRejected: [Function] },
QueueItem {
promise: Promise { state: 0, value: undefined, queue: [] },
callFulfilled: [Function],
callRejected: [Function] } ] }truetrue可以看出,queue 數(shù)組中有兩個(gè)對(duì)象。因?yàn)橐?guī)范中規(guī)定:then 方法可以被同一個(gè) promise 調(diào)用多次。上例中在調(diào)用 .then 和 .catch 時(shí) promise 并沒有被 resolve,所以將 .then 和 .catch 生成的新 promise(a 和 b) 和正確時(shí)的回調(diào)(onSuccess 包裝成 callFulfilled)和錯(cuò)誤時(shí)的回調(diào)(onError 包裝成 callRejected)生成一個(gè) QueueItem 實(shí)例并 push 到 queue 數(shù)組里,所以上面兩個(gè) console.log 打印 true。當(dāng) promise 狀態(tài)改變時(shí)遍歷內(nèi)部 queue 數(shù)組,統(tǒng)一執(zhí)行成功(FULFILLED -> callFulfilled)或失?。≧EJECTED -> callRejected)的回調(diào)(傳入 promise 的 value 值),生成的結(jié)果分別設(shè)置 a 和 b 的 state 和 value,這就是 Promise 實(shí)現(xiàn)的基本原理。
再來看另一個(gè)例子:
var Promise = require('appoint')
var promise = new Promise((resolve) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
promise
.then(() => {})
.then(() => {})
.then(() => {})
console.log(require('util').inspect(promise, { depth: 10 }))打印出:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise: Promise { state: 0, value: undefined, queue: [] },
callFulfilled: [Function],
callRejected: [Function] } ] },
callFulfilled: [Function],
callRejected: [Function] } ] },
callFulfilled: [Function],
callRejected: [Function] } ] }調(diào)用了 3 次 then,每個(gè) then 將它生成的 promise 放到了調(diào)用它的 promise 隊(duì)列里,形成了 3 層調(diào)用關(guān)系。當(dāng)最外層的 promise 狀態(tài)改變時(shí),遍歷它的 queue 數(shù)組調(diào)用對(duì)應(yīng)的回調(diào),設(shè)置子 promise 的 state 和 value 并遍歷它的 queue 數(shù)組調(diào)用對(duì)應(yīng)的回調(diào),然后設(shè)置孫 promise 的 state 和 value 并遍歷它的 queue 數(shù)組調(diào)用對(duì)應(yīng)的回調(diào)......依次類推。
function safelyResolveThen(self, then) {
var called = false;
try {
then(function (value) {
if (called) {
return;
}
called = true;
doResolve(self, value);
}, function (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
});
} catch (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
}
}safelyResolveThen 顧名思義用來『安全的執(zhí)行 then 函數(shù)』,這里的 then 函數(shù)指『第一個(gè)參數(shù)是 resolve 函數(shù)第二個(gè)參數(shù)是 reject 函數(shù)的函數(shù)』,如下兩種情況:
構(gòu)造函數(shù)的參數(shù),即這里的 resolver:
new Promise(function resolver(resolve, reject) {
setTimeout(() => {
resolve('haha')
}, 1000)})promise 的 then:
promise.then(resolve, reject)
safelyResolveThen 有 3 個(gè)作用:
try...catch 捕獲拋出的異常,如:
new Promise(function resolver(resolve, reject) {
throw new Error('Oops')
})called 控制 resolve 或 reject 只執(zhí)行一次,多次調(diào)用沒有任何作用。即:
var Promise = require('appoint')var promise = new Promise(function resolver(resolve, reject) {
setTimeout(() => {
resolve('haha')
}, 1000)
reject('error')})promise.then(console.log)promise.catch(console.error)打印 error,不會(huì)再打印 haha。
沒有錯(cuò)誤則執(zhí)行 doResolve,有錯(cuò)誤則執(zhí)行 doReject。
function doResolve(self, value) {
try {
var then = getThen(value);
if (then) {
safelyResolveThen(self, then);
} else {
self.state = FULFILLED;
self.value = value;
self.queue.forEach(function (queueItem) {
queueItem.callFulfilled(value);
});
}
return self;
} catch (error) {
return doReject(self, error);
}}function doReject(self, error) {
self.state = REJECTED;
self.value = error;
self.queue.forEach(function (queueItem) {
queueItem.callRejected(error);
});
return self;}doReject 就是設(shè)置 promise 的 state 為 REJECTED,value 為 error,callRejected 如前面提到的通知子 promise:『我這里出了點(diǎn)問題呀』然后子 promise 根據(jù)傳入的錯(cuò)誤設(shè)置自己的狀態(tài)和值。doResolve 結(jié)合 safelyResolveThen 使用不斷地解包 promise,直至返回值是非 promise 對(duì)象后,設(shè)置 promise 的狀態(tài)和值,然后通知子 promise:『我這里有值了喲』然后子 promise 根據(jù)傳入的值設(shè)置自己的狀態(tài)和值。
這里有個(gè)輔助函數(shù) getThen:
function getThen(obj) {
var then = obj && obj.then;
if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then)) {
return function appyThen() {
then.apply(obj, arguments);
};
}
}規(guī)范中規(guī)定:如果 then 是函數(shù),將 x(這里是 obj) 作為函數(shù)的 this 調(diào)用。
Promise.prototype.then = function (onFulfilled, onRejected) {
if (!isFunction(onFulfilled) && this.state === FULFILLED ||
!isFunction(onRejected) && this.state === REJECTED) {
return this;
}
var promise = new this.constructor(INTERNAL);
if (this.state !== PENDING) {
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
unwrap(promise, resolver, this.value);
} else {
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
}
return promise;};Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);};上述代碼中的 return this 實(shí)現(xiàn)了值穿透,后面會(huì)講。可以看出,then 方法中生成了一個(gè)新的 promise 然后返回,符合規(guī)范要求。如果 promise 的狀態(tài)改變了,則調(diào)用 unwrap,否則將生成的 promise 加入到當(dāng)前 promise 的回調(diào)隊(duì)列 queue 里,之前講解了如何消費(fèi) queue。有 3 點(diǎn)需要講解:
Promise 構(gòu)造函數(shù)傳入了一個(gè) INTERNAL 即空函數(shù),因?yàn)檫@個(gè)新產(chǎn)生的 promise 可以認(rèn)為是內(nèi)部的 promise,需要根據(jù)外部的 promise 的狀態(tài)和值產(chǎn)生自身的狀態(tài)和值,不需要傳入回調(diào)函數(shù),而外部 Promise 需要傳入回調(diào)函數(shù)決定它的狀態(tài)和值。所以之前 Promise 的構(gòu)造函數(shù)里做了判斷區(qū)分外部調(diào)用還是內(nèi)部調(diào)用:
if (resolver !== INTERNAL) {
safelyResolveThen(this, resolver);}unwrap 代碼如下:
function unwrap(promise, func, value) {
immediate(function () {
var returnValue;
try {
returnValue = func(value);
} catch (error) {
return doReject(promise, error);
}
if (returnValue === promise) {
doReject(promise, new TypeError('Cannot resolve promise with itself'));
} else {
doResolve(promise, returnValue);
}
});}從名字也可以理解是用來解包(即執(zhí)行函數(shù))的,第一個(gè)參數(shù)是子 promise,第二個(gè)參數(shù)是父 promise 的 then 的回調(diào)(onFulfilled/onRejected),第三個(gè)參數(shù)是父 promise 的值(正常值/錯(cuò)誤)。有 3 點(diǎn)需要說明:
使用 immediate 將同步代碼變異步。如:
var Promise = require('appoint')var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)})promise.then(() => {
promise.then(() => {
console.log('1')
})
console.log('2')})打印 2 1,去掉 immediate 則打印 1 2。
try...catch 用來捕獲 then/catch 內(nèi)拋出的異常,并調(diào)用 doReject,如:
promise.then(() => {
throw new Error('haha')})promise.catch(() => {
throw new Error('haha')})返回的值不能是 promise 本身,否則會(huì)造成死循環(huán),如 node@4.6.0 下運(yùn)行:
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var a = promise.then(() => {
return a
})
a.catch(console.log)// [TypeError: Chaining cycle detected for promise #] QueueItem 代碼如下:
function QueueItem(promise, onFulfilled, onRejected) {
this.promise = promise;
this.callFulfilled = function (value) {
doResolve(this.promise, value);
};
this.callRejected = function (error) {
doReject(this.promise, error);
};
if (isFunction(onFulfilled)) {
this.callFulfilled = function (value) {
unwrap(this.promise, onFulfilled, value);
};
}
if (isFunction(onRejected)) {
this.callRejected = function (error) {
unwrap(this.promise, onRejected, error);
};
}}promise 為 then 生成的新 promise(以下稱為『子promise』),onFulfilled 和 onRejected 即是 then 參數(shù)中的 onFulfilled 和 onRejected。從上面代碼可以看出:比如當(dāng) promise 狀態(tài)變?yōu)?FULFILLED 時(shí),之前注冊(cè)的 then 函數(shù),用 callFulfilled 調(diào)用 unwrap 進(jìn)行解包最終得出子 promise 的狀態(tài)和值,之前注冊(cè)的 catch 函數(shù),用 callFulfilled 直接調(diào)用 doResolve,設(shè)置隊(duì)列里子 promise 的狀態(tài)和值。當(dāng) promise 狀態(tài)變?yōu)?REJECTED 類似。
注意:promise.catch(onRejected) 就是 promise.then(null, onRejected) 的語(yǔ)法糖。
至此,Promise 的核心實(shí)現(xiàn)都完成了。
Promise.prototype.then = function (onFulfilled, onRejected) {
if (!isFunction(onFulfilled) && this.state === FULFILLED ||
!isFunction(onRejected) && this.state === REJECTED) {
return this;
}
...};上面提到了值穿透問題,值穿透即:
var Promise = require('appoint')
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
promise
.then('hehe')
.then(console.log)最終打印 haha 而不是 hehe。
通過 return this 只實(shí)現(xiàn)了值穿透的一種情況,其實(shí)值穿透有兩種情況:
promise 已經(jīng)是 FULFILLED/REJECTED 時(shí),通過 return this 實(shí)現(xiàn)的值穿透:
var Promise = require('appoint')var promise = new Promise(function (resolve) {
setTimeout(() => {
resolve('haha')
}, 1000)})promise.then(() => {
promise.then().then((res) => {// ①
console.log(res)// haha
})
promise.catch().then((res) => {// ②
console.log(res)// haha
})
console.log(promise.then() === promise.catch())// true
console.log(promise.then(1) === promise.catch({ name: 'nswbmw' }))// true})上述代碼①②處 promise 已經(jīng)是 FULFILLED 了符合條件所以執(zhí)行了 return this。注意:原生的 Promise 實(shí)現(xiàn)里并不是這樣實(shí)現(xiàn)的,所以會(huì)打印兩個(gè) false。
promise 是 PENDING 時(shí),通過生成新的 promise 加入到父 promise 的 queue,父 promise 有值時(shí)調(diào)用 callFulfilled->doResolve 或 callRejected->doReject(因?yàn)?then/catch 傳入的參數(shù)不是函數(shù))設(shè)置子 promise 的狀態(tài)和值為父 promise 的狀態(tài)和值。如:
var Promise = require('appoint')var promise = new Promise((resolve) => {
setTimeout(() => {
resolve('haha')
}, 1000)})var a = promise.then()a.then((res) => {
console.log(res)// haha})var b = promise.catch()b.then((res) => {
console.log(res)// haha})console.log(a === b)// falsePromise.resolve = resolve;function resolve(value) {
if (value instanceof this) {
return value;
}
return doResolve(new this(INTERNAL), value);}Promise.reject = reject;function reject(reason) {
var promise = new this(INTERNAL);
return doReject(promise, reason);}當(dāng) Promise.resolve 參數(shù)是一個(gè) promise 時(shí),直接返回該值。
Promise.all = all;function all(iterable) {
var self = this;
if (!isArray(iterable)) {
return this.reject(new TypeError('must be an array'));
}
var len = iterable.length;
var called = false;
if (!len) {
return this.resolve([]);
}
var values = new Array(len);
var resolved = 0;
var i = -1;
var promise = new this(INTERNAL);
while (++i < len) {
allResolver(iterable[i], i);
}
return promise;
function allResolver(value, i) {
self.resolve(value).then(resolveFromAll, function (error) {
if (!called) {
called = true;
doReject(promise, error);
}
});
function resolveFromAll(outValue) {
values[i] = outValue;
if (++resolved === len && !called) {
called = true;
doResolve(promise, values);
}
}
}}Promise.all 用來并行執(zhí)行多個(gè) promise/值,當(dāng)所有 promise/值執(zhí)行完畢后或有一個(gè)發(fā)生錯(cuò)誤時(shí)返回??梢钥闯觯?/p>
Promise.all 內(nèi)部生成了一個(gè)新的 promise 返回。
called 用來控制即使有多個(gè) promise reject 也只有第一個(gè)生效。
values 用來存儲(chǔ)結(jié)果。
當(dāng)最后一個(gè) promise 得出結(jié)果后,使用 doResolve(promise, values) 設(shè)置 promise 的 state 為 FULFILLED,value 為結(jié)果數(shù)組 values。
Promise.race = race;function race(iterable) {
var self = this;
if (!isArray(iterable)) {
return this.reject(new TypeError('must be an array'));
}
var len = iterable.length;
var called = false;
if (!len) {
return this.resolve([]);
}
var i = -1;
var promise = new this(INTERNAL);
while (++i < len) {
resolver(iterable[i]);
}
return promise;
function resolver(value) {
self.resolve(value).then(function (response) {
if (!called) {
called = true;
doResolve(promise, response);
}
}, function (error) {
if (!called) {
called = true;
doReject(promise, error);
}
});
}}Promise.race 接受一個(gè)數(shù)組,當(dāng)數(shù)組中有一個(gè) resolve 或 reject 時(shí)返回。跟 Promise.all 代碼相近,只不過這里用 called 控制只要有任何一個(gè) promise onFulfilled/onRejected 立即去設(shè)置 promise 的狀態(tài)和值。
至此,Promise 的實(shí)現(xiàn)全部講解完畢。
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開啟,新人活動(dòng)云服務(wù)器買多久送多久。