前段時間我用兩篇文章深刻講解了異步的概念和Event Loop的底層原理,而後還講了一種本身實現異步的發佈訂閱模式:javascript
setTimeout和setImmediate到底誰先執行,本文讓你完全理解Event Loopjava
從發佈訂閱模式入手讀懂Node.js的EventEmitter源碼git
本文會講解另外一種更現代的異步實現方案:Promise
。Promise幾乎是面試必考點,因此咱們不能僅僅會用,還得知道他的底層原理,學習他原理的最好方法就是本身也實現一個Promise。因此本文會本身實現一個遵循Promise/A+
規範的Promise。實現以後,咱們還要用Promise/A+
官方的測試工具來測試下咱們的實現是否正確,這個工具總共有872個測試用例,所有經過纔算是符合Promise/A+
規範,下面是他們的連接:github
Promise/A+
規範: github.com/promises-ap…面試
Promise/A+
測試工具: github.com/promises-ap…npm
本文的完整代碼託管在GitHub上: github.com/dennis-jian…json
Promise的基本用法,網上有不少,我這裏簡單提一下,我仍是用三個相互依賴的網絡請求作例子,假如咱們有三個網絡請求,請求2必須依賴請求1的結果,請求3必須依賴請求2的結果,若是用回調的話會有三層,會陷入「回調地獄」,用Promise就清晰多了:數組
const request = require("request");
// 咱們先用Promise包裝下三個網絡請求
// 請求成功時resolve這個Promise
const request1 = function() {
const promise = new Promise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request1 success');
}
});
});
return promise;
}
const request2 = function() {
const promise = new Promise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request2 success');
}
});
});
return promise;
}
const request3 = function() {
const promise = new Promise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request3 success');
}
});
});
return promise;
}
// 先發起request1,等他resolve後再發起request2,
// 而後是request3
request1().then((data) => {
console.log(data);
return request2();
})
.then((data) => {
console.log(data);
return request3();
})
.then((data) => {
console.log(data);
})
複製代碼
上面的例子裏面,then
是能夠鏈式調用的,後面的then
能夠拿到前面resolve
出來的數據,咱們控制檯能夠看到三個success依次打出來:promise
經過上面的例子,其實咱們已經知道了一個promise長什麼樣子,Promises/A+規範其實就是對這個長相進一步進行了規範。下面我會對這個規範進行一些講解。網絡
promise
:是一個擁有then
方法的對象或函數,其行爲符合本規範
thenable
:是一個定義了then
方法的對象或函數。這個主要是用來兼容一些老的Promise實現,只要一個Promise實現是thenable,也就是擁有then
方法的,就能夠跟Promises/A+兼容。
value
:指reslove
出來的值,能夠是任何合法的JS值(包括undefined
, thenable 和 promise等)
exception
:異常,在Promise裏面用throw
拋出來的值
reason
:拒絕緣由,是reject
裏面傳的參數,表示reject
的緣由
Promise總共有三個狀態:
pending
: 一個promise在resolve或者reject前就處於這個狀態。fulfilled
: 一個promise被resolve後就處於fulfilled
狀態,這個狀態不能再改變,並且必須擁有一個不可變的值(value
)。rejected
: 一個promise被reject後就處於rejected
狀態,這個狀態也不能再改變,並且必須擁有一個不可變的拒絕緣由(reason
)。
注意這裏的不可變指的是===
,也就是說,若是value
或者reason
是對象,只要保證引用不變就行,規範沒有強制要求裏面的屬性也不變。Promise狀態其實很簡單,畫張圖就是:
一個promise必須擁有一個then
方法來訪問他的值或者拒絕緣由。then
方法有兩個參數:
promise.then(onFulfilled, onRejected)
複製代碼
onFulfilled
和 onRejected
都是可選參數。
onFulfilled
不是函數,其必須被忽略onRejected
不是函數,其必須被忽略onFulfilled
特性若是 onFulfilled
是函數:
promise
執行結束後其必須被調用,其第一個參數爲 promise
的終值value
promise
執行結束前其不可被調用onRejected
特性若是 onRejected
是函數:
promise
被拒絕執行後其必須被調用,其第一個參數爲 promise
的據因reason
promise
被拒絕執行前其不可被調用then
方法能夠被同一個 promise
調用屢次
promise
成功執行時,全部 onFulfilled
需按照其註冊順序依次回調promise
被拒絕執行時,全部的 onRejected
需按照其註冊順序依次回調then
方法必須返回一個 promise
對象。
promise2 = promise1.then(onFulfilled, onRejected);
複製代碼
onFulfilled
或者 onRejected
返回一個值 x
,則運行 Promise 解決過程:[[Resolve]](promise2, x)
onFulfilled
或者 onRejected
拋出一個異常 e
,則 promise2
必須拒絕執行,並返回拒因 e
onFulfilled
不是函數且 promise1
成功執行, promise2
必須成功執行並返回相同的值onRejected
不是函數且 promise1
拒絕執行, promise2
必須拒絕執行並返回相同的據因規範裏面還有很大一部分是講解Promise 解決過程的,光看規範,很空洞,前面這些規範已經能夠指導咱們開始寫一個本身的Promise了,Promise 解決過程會在咱們後面寫到了再詳細講解。
咱們本身要寫一個Promise,確定須要知道有哪些工做須要作,咱們先從Promise的使用來窺探下須要作啥:
- 新建Promise須要使用
new
關鍵字,那他確定是做爲面向對象的方式調用的,Promise是一個類。關於JS的面向對象更詳細的解釋能夠看這篇文章。- 咱們
new Promise(fn)
的時候須要傳一個函數進去,說明Promise的參數是一個函數- 構造函數傳進去的
fn
會收到resolve
和reject
兩個函數,用來表示Promise成功和失敗,說明構造函數裏面還須要resolve
和reject
這兩個函數,這兩個函數的做用是改變Promise的狀態。- 根據規範,promise有
pending
,fulfilled
,rejected
三個狀態,初始狀態爲pending
,調用resolve
會將其改成fulfilled
,調用reject
會改成rejected
。- promise實例對象建好後能夠調用
then
方法,並且是能夠鏈式調用then
方法,說明then
是一個實例方法。鏈式調用的實現這篇有詳細解釋,我這裏再也不贅述。簡單的說就是then
方法也必須返回一個帶then
方法的對象,能夠是this或者新的promise實例。
爲了更好的兼容性,本文就不用ES6了。
// 先定義三個常量表示狀態
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';
function MyPromise(fn) {
this.status = PENDING; // 初始狀態爲pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
}
複製代碼
resolve
和reject
方法根據規範,resolve
方法是將狀態改成fulfilled,reject
是將狀態改成rejected。
// 這兩個方法直接寫在構造函數裏面
function MyPromise(fn) {
// ...省略前面代碼...
// 存一下this,以便resolve和reject裏面訪問
var that = this;
// resolve方法參數是value
function resolve(value) {
if(that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
}
}
// reject方法參數是reason
function reject(reason) {
if(that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
}
}
}
複製代碼
最後將resolve
和reject
做爲參數調用傳進來的參數,記得加上try
,若是捕獲到錯誤就reject
。
function MyPromise(fn) {
// ...省略前面代碼...
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
複製代碼
then
方法根據咱們前面的分析,then
方法能夠鏈式調用,因此他是實例方法,並且規範中的API是promise.then(onFulfilled, onRejected)
,咱們先把架子搭出來:
MyPromise.prototype.then = function(onFulfilled, onRejected) {}
複製代碼
那then
方法裏面應該幹什麼呢,其實規範也告訴咱們了,先檢查onFulfilled
和onRejected
是否是函數,若是不是函數就忽略他們,所謂「忽略」並非什麼都不幹,對於onFulfilled
來講「忽略」就是將value
原封不動的返回,對於onRejected
來講就是返回reason
,onRejected
由於是錯誤分支,咱們返回reason
應該throw一個Error:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 若是onFulfilled不是函數,給一個默認函數,返回value
var realOnFulfilled = onFulfilled;
if(typeof realOnFulfilled !== 'function') {
realOnFulfilled = function (value) {
return value;
}
}
// 若是onRejected不是函數,給一個默認函數,返回reason的Error
var realOnRejected = onRejected;
if(typeof realOnRejected !== 'function') {
realOnRejected = function (reason) {
throw reason;
}
}
}
複製代碼
參數檢查完後就該乾點真正的事情了,想一想咱們使用Promise的時候,若是promise操做成功了就會調用then
裏面的onFulfilled
,若是他失敗了,就會調用onRejected
。對應咱們的代碼就應該檢查下promise的status,若是是FULFILLED
,就調用onFulfilled
,若是是REJECTED
,就調用onRejected
:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// ...省略前面代碼...
if(this.status === FULFILLED) {
onFulfilled(this.value)
}
if(this.status === REJECTED) {
onRejected(this.reason);
}
}
複製代碼
再想一下,咱們新建一個promise的時候多是直接這樣用的:
new Promise(fn).then(onFulfilled, onRejected);
複製代碼
上面代碼then
是在實例對象一建立好就調用了,這時候fn
裏面的異步操做可能還沒結束呢,也就是說他的status
仍是PENDING
,這怎麼辦呢,這時候咱們確定不能當即調onFulfilled
或者onRejected
的,由於fn
到底成功仍是失敗還不知道呢。那何時知道fn
成功仍是失敗呢?答案是fn
裏面主動調resolve
或者reject
的時候。因此若是這時候status
狀態仍是PENDING
,咱們應該將onFulfilled
和onRejected
兩個回調存起來,等到fn
有告終論,resolve
或者reject
的時候再來調用對應的代碼。由於後面then
還有鏈式調用,會有多個onFulfilled
和onRejected
,我這裏用兩個數組將他們存起來,等resolve
或者reject
的時候將數組裏面的所有方法拿出來執行一遍:
// 構造函數
function MyPromise(fn) {
// ...省略其餘代碼...
// 構造函數裏面添加兩個數組存儲成功和失敗的回調
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value) {
if(that.status === PENDING) {
// ...省略其餘代碼...
// resolve裏面將全部成功的回調拿出來執行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}
function reject(reason) {
if(that.status === PENDING) {
// ...省略其餘代碼...
// resolve裏面將全部失敗的回調拿出來執行
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}
}
// then方法
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// ...省略其餘代碼...
// 若是仍是PENDING狀態,將回調保存下來
if(this.status === PENDING) {
this.onFulfilledCallbacks.push(realOnFulfilled);
this.onRejectedCallbacks.push(realOnRejected);
}
}
複製代碼
上面這種暫時將回調保存下來,等條件知足的時候再拿出來運行讓我想起了一種模式:訂閱發佈模式。咱們往回調數組裏面push
回調函數,其實就至關於往事件中心註冊事件了,resolve
就至關於發佈了一個成功事件,全部註冊了的事件,即onFulfilledCallbacks
裏面的全部方法都會拿出來執行,同理reject
就至關於發佈了一個失敗事件。更多訂閱發佈模式的原理能夠看這裏。
到這裏爲止,其實咱們已經能夠實現異步調用了,只是then
的返回值還沒實現,還不能實現鏈式調用,咱們先來玩一下:
var request = require("request");
var MyPromise = require('./MyPromise');
var promise1 = new MyPromise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request1 success');
}
});
});
promise1.then(function(value) {
console.log(value);
});
var promise2 = new MyPromise((resolve, reject) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
reject('request2 failed');
}
});
});
promise2.then(function(value) {
console.log(value);
}, function(reason) {
console.log(reason);
});
複製代碼
上述代碼輸出以下圖,符合咱們的預期,說明到目前爲止,咱們的代碼都沒問題:
then
的返回值根據規範then
的返回值必須是一個promise,規範還定義了不一樣狀況應該怎麼處理,咱們先來處理幾種比較簡單的狀況:
onFulfilled
或者 onRejected
拋出一個異常 e
,則 promise2
必須拒絕執行,並返回拒因 e
。MyPromise.prototype.then = function(onFulfilled, onRejected) {
// ... 省略其餘代碼 ...
// 有了這個要求,在RESOLVED和REJECTED的時候就不能簡單的運行onFulfilled和onRejected了。
// 咱們須要將他們用try...catch...包起來,若是有錯就reject。
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
realOnFulfilled(that.value);
} catch (error) {
reject(error);
}
});
return promise2;
}
if(this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
realOnRejected(that.reason);
} catch (error) {
reject(error);
}
});
return promise2;
}
// 若是仍是PENDING狀態,也不能直接保存回調方法了,須要包一層來捕獲錯誤
if(this.status === PENDING) {
var promise2 = new MyPromise(function(resolve, reject) {
that.onFulfilledCallbacks.push(function() {
try {
realOnFulfilled(that.value);
} catch (error) {
reject(error);
}
});
that.onRejectedCallbacks.push(function() {
try {
realOnRejected(that.reason);
} catch (error) {
reject(error);
}
});
});
return promise2;
}
}
複製代碼
onFulfilled
不是函數且 promise1
成功執行, promise2
必須成功執行並返回相同的值// 咱們就根據要求加個判斷,注意else裏面是正常執行流程,須要resolve
// 這是個例子,每一個realOnFulfilled後面都要這樣寫
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
realOnFulfilled(that.value);
resolve(that.value);
}
} catch (error) {
reject(error);
}
});
return promise2;
}
複製代碼
onRejected
不是函數且 promise1
拒絕執行, promise2
必須拒絕執行並返回相同的據因。這個要求其實在咱們檢測 onRejected
不是函數的時候已經作到了,由於咱們默認給的onRejected
裏面會throw一個Error,因此代碼確定會走到catch裏面去。可是咱們爲了更直觀,代碼仍是跟規範一一對應吧。須要注意的是,若是promise1
的onRejected
執行成功了,promise2
應該被resolve
。改造代碼以下:if(this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
if(typeof onRejected !== 'function') {
reject(that.reason);
} else {
realOnRejected(that.reason);
resolve();
}
} catch (error) {
reject(error);
}
});
return promise2;
}
複製代碼
onFulfilled
或者 onRejected
返回一個值 x
,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)
。這條其實才是規範的第一條,由於他比較麻煩,因此我將它放到了最後。前面咱們代碼的實現,其實只要onRejected
或者onFulfilled
成功執行了,咱們都要resolve promise2
。多了這條,咱們還須要對onRejected
或者onFulfilled
的返回值進行判斷,若是有返回值就要進行 Promise 解決過程。咱們專門寫一個方法來進行Promise 解決過程。前面咱們代碼的實現,其實只要onRejected
或者onFulfilled
成功執行了,咱們都要resolve promise2
,這個過程咱們也放到這個方法裏面去吧,因此代碼變爲下面這樣,其餘地方相似:if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject); // 調用Promise 解決過程
}
} catch (error) {
reject(error);
}
});
return promise2;
}
複製代碼
如今咱們該來實現resolvePromise
方法了,規範中這一部分較長,我就直接把規範做爲註釋寫在代碼裏面了。
function resolvePromise(promise, x, resolve, reject) {
// 若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise
// 這是爲了防止死循環
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof MyPromise) {
// 若是 x 爲 Promise ,則使 promise 接受 x 的狀態
// 也就是繼續執行x,若是執行的時候拿到一個y,還要繼續解析y
// 這個if跟下面判斷then而後拿到執行其實重複了,無關緊要
x.then(function (y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
}
// 若是 x 爲對象或者函數
else if (typeof x === 'object' || typeof x === 'function') {
// 這個坑是跑測試的時候發現的,若是x是null,應該直接resolve
if (x === null) {
return resolve(x);
}
try {
// 把 x.then 賦值給 then
var then = x.then;
} catch (error) {
// 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
return reject(error);
}
// 若是 then 是函數
if (typeof then === 'function') {
var called = false;
// 將 x 做爲函數的做用域 this 調用之
// 傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise
// 名字重名了,我直接用匿名函數了
try {
then.call(
x,
// 若是 resolvePromise 以值 y 爲參數被調用,則運行 [[Resolve]](promise, y)
function (y) {
// 若是 resolvePromise 和 rejectPromise 均被調用,
// 或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用
// 實現這條須要前面加一個變量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 若是 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
function (r) {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 若是調用 then 方法拋出了異常 e:
// 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之
if (called) return;
// 不然以 e 爲據因拒絕 promise
reject(error);
}
} else {
// 若是 then 不是函數,以 x 爲參數執行 promise
resolve(x);
}
} else {
// 若是 x 不爲對象或者函數,以 x 爲參數執行 promise
resolve(x);
}
}
複製代碼
onFulfilled
和 onRejected
的執行時機在規範中還有一條:onFulfilled
和 onRejected
只有在執行環境堆棧僅包含平臺代碼時纔可被調用。這一條的意思是實踐中要確保 onFulfilled
和 onRejected
方法異步執行,且應該在 then
方法被調用的那一輪事件循環以後的新執行棧中執行。因此在咱們執行onFulfilled
和 onRejected
的時候都應該包到setTimeout
裏面去。
// 這塊代碼在then裏面
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
// 這裏加setTimeout
setTimeout(function() {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if(this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
// 這裏加setTimeout
setTimeout(function() {
try {
if(typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if (this.status === PENDING) {
var promise2 = new MyPromise(function (resolve, reject) {
that.onFulfilledCallbacks.push(function () {
// 這裏加setTimeout
setTimeout(function () {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
that.onRejectedCallbacks.push(function () {
// 這裏加setTimeout
setTimeout(function () {
try {
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0)
});
});
return promise2;
}
複製代碼
咱們使用Promise/A+官方的測試工具promises-aplus-tests來對咱們的MyPromise
進行測試,要使用這個工具咱們必須實現一個靜態方法deferred
,官方對這個方法的定義以下:
deferred
: 返回一個包含{ promise, resolve, reject }的對象
promise
是一個處於pending
狀態的promise
resolve(value)
用value
解決上面那個promise
reject(reason)
用reason
拒絕上面那個promise
咱們實現代碼以下:
MyPromise.deferred = function() {
var result = {};
result.promise = new MyPromise(function(resolve, reject){
result.resolve = resolve;
result.reject = reject;
});
return result;
}
複製代碼
而後用npm將promises-aplus-tests
下載下來,再配置下package.json就能夠跑測試了:
{
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
},
"scripts": {
"test": "promises-aplus-tests MyPromise"
}
}
複製代碼
在跑測試的時候發現一個坑,在resolvePromise
的時候,若是x是null
,他的類型也是object
,是應該直接用x來resolve的,以前的代碼會走到catch
而後reject
,因此須要檢測下null
:
// 這個坑是跑測試的時候發現的,若是x是null,應該直接resolve
if(x === null) {
return resolve(x);
}
複製代碼
這個測試總共872用例,咱們寫的Promise完美經過了全部用例:
在ES6的官方Promise還有不少API,好比:
Promise.resolve
Promise.reject
Promise.all
Promise.race
Promise.prototype.catch
Promise.prototype.finally
Promise.allSettled
雖然這些都不在Promise/A+裏面,可是咱們也來實現一下吧,加深理解。其實咱們前面實現了Promise/A+再來實現這些已是小菜一碟了,由於這些API所有是前面的封裝而已。
將現有對象轉爲Promise對象,若是 Promise.resolve 方法的參數,不是具備 then 方法的對象(又稱 thenable 對象),則返回一個新的 Promise 對象,且它的狀態爲fulfilled。
MyPromise.resolve = function(parameter) {
if(parameter instanceof MyPromise) {
return parameter;
}
return new MyPromise(function(resolve) {
resolve(parameter);
});
}
複製代碼
返回一個新的Promise實例,該實例的狀態爲rejected。Promise.reject方法的參數reason,會被傳遞給實例的回調函數。
MyPromise.reject = function(reason) {
return new MyPromise(function(resolve, reject) {
reject(reason);
});
}
複製代碼
該方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3]);
複製代碼
Promise.all()
方法接受一個數組做爲參數,p1
、p2
、p3
都是 Promise 實例,若是不是,就會先調用Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。當p1, p2, p3所有resolve,大的promise才resolve,有任何一個reject,大的promise都reject。
MyPromise.all = function(promiseList) {
var resPromise = new MyPromise(function(resolve, reject) {
var count = 0;
var result = [];
var length = promiseList.length;
if(length === 0) {
return resolve(result);
}
promiseList.forEach(function(promise, index) {
MyPromise.resolve(promise).then(function(value){
count++;
result[index] = value;
if(count === length) {
resolve(result);
}
}, function(reason){
reject(reason);
});
});
});
return resPromise;
}
複製代碼
用法:
const p = Promise.race([p1, p2, p3]);
複製代碼
該方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。上面代碼中,只要p1
、p2
、p3
之中有一個實例率先改變狀態,p
的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p
的回調函數。
MyPromise.race = function(promiseList) {
var resPromise = new MyPromise(function(resolve, reject) {
var length = promiseList.length;
if(length === 0) {
return resolve();
} else {
for(var i = 0; i < length; i++) {
MyPromise.resolve(promiseList[i]).then(function(value) {
return resolve(value);
}, function(reason) {
return reject(reason);
});
}
}
});
return resPromise;
}
複製代碼
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的別名,用於指定發生錯誤時的回調函數。
MyPromise.prototype.catch = function(onRejected) {
this.then(null, onRejected);
}
複製代碼
finally
方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。
MyPromise.prototype.finally = function(fn) {
return this.then(function(value){
return MyPromise.resolve(value).then(function(){
return value;
});
}, function(error){
return MyPromise.resolve(reason).then(function() {
throw error
});
});
}
複製代碼
該方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只有等到全部這些參數實例都返回結果,不論是fulfilled
仍是rejected
,包裝實例纔會結束。該方法由 ES2020 引入。該方法返回的新的 Promise 實例,一旦結束,狀態老是fulfilled
,不會變成rejected
。狀態變成fulfilled
後,Promise 的監聽函數接收到的參數是一個數組,每一個成員對應一個傳入Promise.allSettled()
的 Promise 實例的執行結果。
MyPromise.allSettled = function(promiseList) {
return new MyPromise(function(resolve){
var length = promiseList.length;
var result = [];
var count = 0;
if(length === 0) {
return resolve(result);
} else {
for(var i = 0; i < length; i++) {
(function(i){
var currentPromise = MyPromise.resolve(promiseList[i]);
currentPromise.then(function(value){
count++;
result[i] = {
status: 'fulfilled',
value: value
}
if(count === length) {
return resolve(result);
}
}, function(reason){
count++;
result[i] = {
status: 'rejected',
reason: reason
}
if(count === length) {
return resolve(result);
}
});
})(i)
}
}
});
}
複製代碼
徹底版的代碼較長,這裏若是看不清楚的能夠去個人GitHub上看:
// 先定義三個常量表示狀態
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';
function MyPromise(fn) {
this.status = PENDING; // 初始狀態爲pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
// 構造函數裏面添加兩個數組存儲成功和失敗的回調
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 存一下this,以便resolve和reject裏面訪問
var that = this;
// resolve方法參數是value
function resolve(value) {
if (that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
// resolve裏面將全部成功的回調拿出來執行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}
// reject方法參數是reason
function reject(reason) {
if (that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
// resolve裏面將全部失敗的回調拿出來執行
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
function resolvePromise(promise, x, resolve, reject) {
// 若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise
// 這是爲了防止死循環
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof MyPromise) {
// 若是 x 爲 Promise ,則使 promise 接受 x 的狀態
// 也就是繼續執行x,若是執行的時候拿到一個y,還要繼續解析y
// 這個if跟下面判斷then而後拿到執行其實重複了,無關緊要
x.then(function (y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
}
// 若是 x 爲對象或者函數
else if (typeof x === 'object' || typeof x === 'function') {
// 這個坑是跑測試的時候發現的,若是x是null,應該直接resolve
if (x === null) {
return resolve(x);
}
try {
// 把 x.then 賦值給 then
var then = x.then;
} catch (error) {
// 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
return reject(error);
}
// 若是 then 是函數
if (typeof then === 'function') {
var called = false;
// 將 x 做爲函數的做用域 this 調用之
// 傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise
// 名字重名了,我直接用匿名函數了
try {
then.call(
x,
// 若是 resolvePromise 以值 y 爲參數被調用,則運行 [[Resolve]](promise, y)
function (y) {
// 若是 resolvePromise 和 rejectPromise 均被調用,
// 或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用
// 實現這條須要前面加一個變量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 若是 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
function (r) {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 若是調用 then 方法拋出了異常 e:
// 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之
if (called) return;
// 不然以 e 爲據因拒絕 promise
reject(error);
}
} else {
// 若是 then 不是函數,以 x 爲參數執行 promise
resolve(x);
}
} else {
// 若是 x 不爲對象或者函數,以 x 爲參數執行 promise
resolve(x);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// 若是onFulfilled不是函數,給一個默認函數,返回value
// 後面返回新promise的時候也作了onFulfilled的參數檢查,這裏能夠刪除,暫時保留是爲了跟規範一一對應,看得更直觀
var realOnFulfilled = onFulfilled;
if (typeof realOnFulfilled !== 'function') {
realOnFulfilled = function (value) {
return value;
}
}
// 若是onRejected不是函數,給一個默認函數,返回reason的Error
// 後面返回新promise的時候也作了onRejected的參數檢查,這裏能夠刪除,暫時保留是爲了跟規範一一對應,看得更直觀
var realOnRejected = onRejected;
if (typeof realOnRejected !== 'function') {
realOnRejected = function (reason) {
throw reason;
}
}
var that = this; // 保存一下this
if (this.status === FULFILLED) {
var promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if (this.status === REJECTED) {
var promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
// 若是仍是PENDING狀態,將回調保存下來
if (this.status === PENDING) {
var promise2 = new MyPromise(function (resolve, reject) {
that.onFulfilledCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
that.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0)
});
});
return promise2;
}
}
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
MyPromise.resolve = function (parameter) {
if (parameter instanceof MyPromise) {
return parameter;
}
return new MyPromise(function (resolve) {
resolve(parameter);
});
}
MyPromise.reject = function (reason) {
return new MyPromise(function (resolve, reject) {
reject(reason);
});
}
MyPromise.all = function (promiseList) {
var resPromise = new MyPromise(function (resolve, reject) {
var count = 0;
var result = [];
var length = promiseList.length;
if (length === 0) {
return resolve(result);
}
promiseList.forEach(function (promise, index) {
MyPromise.resolve(promise).then(function (value) {
count++;
result[index] = value;
if (count === length) {
resolve(result);
}
}, function (reason) {
reject(reason);
});
});
});
return resPromise;
}
MyPromise.race = function (promiseList) {
var resPromise = new MyPromise(function (resolve, reject) {
var length = promiseList.length;
if (length === 0) {
return resolve();
} else {
for (var i = 0; i < length; i++) {
MyPromise.resolve(promiseList[i]).then(function (value) {
return resolve(value);
}, function (reason) {
return reject(reason);
});
}
}
});
return resPromise;
}
MyPromise.prototype.catch = function (onRejected) {
this.then(null, onRejected);
}
MyPromise.prototype.finally = function (fn) {
return this.then(function (value) {
return MyPromise.resolve(fn()).then(function () {
return value;
});
}, function (error) {
return MyPromise.resolve(fn()).then(function () {
throw error
});
});
}
MyPromise.allSettled = function (promiseList) {
return new MyPromise(function (resolve) {
var length = promiseList.length;
var result = [];
var count = 0;
if (length === 0) {
return resolve(result);
} else {
for (var i = 0; i < length; i++) {
(function (i) {
var currentPromise = MyPromise.resolve(promiseList[i]);
currentPromise.then(function (value) {
count++;
result[i] = {
status: 'fulfilled',
value: value
}
if (count === length) {
return resolve(result);
}
}, function (reason) {
count++;
result[i] = {
status: 'rejected',
reason: reason
}
if (count === length) {
return resolve(result);
}
});
})(i)
}
}
});
}
module.exports = MyPromise;
複製代碼
至此,咱們的Promise就簡單實現了,只是咱們不是原生代碼,不能作成微任務,若是必定要作成微任務的話,只能用其餘微任務API模擬,好比MutaionObserver
或者process.nextTick
。下面再回顧下幾個要點:
then
方法對於還在pending
的任務,實際上是將回調函數onFilfilled
和onRejected
塞入了兩個數組resolve
方法會將數組onFilfilledCallbacks
裏面的方法所有拿出來執行,這裏面是以前then方法塞進去的成功回調reject
方法會將數組onRejectedCallbacks
裏面的方法所有拿出來執行,這裏面是以前then方法塞進去的失敗回調then
方法會返回一個新的Promise以便執行鏈式調用catch
和finally
這些實例方法都必須返回一個新的Promise實例以便實現鏈式調用文章的最後,感謝你花費寶貴的時間閱讀本文,若是本文給了你一點點幫助或者啓發,請不要吝嗇你的贊和GitHub小星星,你的支持是做者持續創做的動力。
做者博文GitHub項目地址: github.com/dennis-jian…
做者掘金文章彙總:juejin.im/post/5e3ffc…