前段時間我用兩篇文章深入講解了異步的概念和Event Loop的底層原理,然后還講了一種自己實現異步的發布訂閱模式:
setTimeout和setImmediate到底誰先執行,本文讓你徹底理解Event Loop
從發布訂閱模式入手讀懂Node.js的EventEmitter源碼
本文會講解另一種更現代的異步實現方案:Promise。Promise幾乎是面試必考點,所以我們不能僅僅會用,還得知道他的底層原理,學習他原理的最好方法就是自己也實現一個Promise。所以本文會自己實現一個遵循Promise/A+規范的Promise。實現之后,我們還要用Promise/A+官方的測試工具來測試下我們的實現是否正確,這個工具總共有872個測試用例,全部通過才算是符合Promise/A+規范,下面是他們的鏈接:
Promise/A+規范: https://github.com/promises-aplus/promises-spec
Promise/A+測試工具: https://github.com/promises-aplus/promises-tests
本文的完整代碼托管在GitHub上: https://github.com/dennis-jiang/Front-End-Knowledges/blob/master/Examples/JavaScript/Promise/MyPromise.js
Promise用法
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依次打出來:

Promises/A+規范
通過上面的例子,其實我們已經知道了一個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狀態
Promise總共有三個狀態:
pending: 一個promise在resolve或者reject前就處於這個狀態。fulfilled: 一個promise被resolve后就處於fulfilled狀態,這個狀態不能再改變,而且必須擁有一個不可變的值(value)。rejected: 一個promise被reject后就處於rejected狀態,這個狀態也不能再改變,而且必須擁有一個不可變的拒絕原因(reason)。
注意這里的不可變指的是===,也就是說,如果value或者reason是對象,只要保證引用不變就行,規范沒有強制要求里面的屬性也不變。Promise狀態其實很簡單,畫張圖就是:

then方法
一個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的使用來窺探下需要做啥:
- 新建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) {
if(reason instanceof Error) {
throw reason;
} else {
throw new Error(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;
}
Promise 解決過程
現在我們該來實現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'));
}
// 如果 x 為 Promise ,則使 promise 接受 x 的狀態
if(x instanceof MyPromise) {
// 如果 x 處於等待態, promise 需保持為等待態直至 x 被執行或拒絕
if(x.status === PENDING) {
x.then(resolve, reject);
} else if(x.status === FULFILLED) {
// 如果 x 處於執行態,用相同的值執行 promise
resolve(x.value);
} else if(x.status === REJECTED) {
// 如果 x 處於拒絕態,用相同的據因拒絕 promise
reject(x.reason);
}
}
// 如果 x 為對象或者函數
else if(typeof x === 'object' || typeof x === 'function') {
try {
// 把 x.then 賦值給 then
var then = x.then;
} catch (error) {
// 如果取 x.then 的值時拋出錯誤 e ,則以 e 為據因拒絕 promise
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);
}
}
處理同步任務
到這里我們的Promise/A+基本都實現了,只是還要注意一個點,如果用戶給構造函數傳的是一個同步函數,里面的resolve和reject會立即執行,比then還執行的早,那then里面注冊的回調就沒機會運行了,所以要給他們加個setTimeout:
function resolve(value) {
// 這里加setTimeout
setTimeout(function() {
if(that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}, 0);
}
function reject(reason) {
// 這里加setTimeout
setTimeout(function() {
if(that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}, 0);
}
測試我們的Promise
我們使用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完美通過了所有用例:

其他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.resolve
將現有對象轉為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.reject
返回一個新的Promise實例,該實例的狀態為rejected。Promise.reject方法的參數reason,會被傳遞給實例的回調函數。
MyPromise.reject = function(reason) {
return new MyPromise(function(resolve, reject) {
reject(reason);
});
}
Promise.all
該方法用於將多個 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;
}
Promise.race
用法:
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
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。
MyPromise.prototype.catch = function(onRejected) {
this.then(null, onRejected);
}
Promise.prototype.finally
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.allSettled
該方法接受一組 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) {
setTimeout(function() {
if(that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
// resolve里面將所有成功的回調拿出來執行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}, 0);
}
// reject方法參數是reason
function reject(reason) {
setTimeout(function() {
if(that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
// resolve里面將所有失敗的回調拿出來執行
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}, 0);
}
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'));
}
// 如果 x 為 Promise ,則使 promise 接受 x 的狀態
if(x instanceof MyPromise) {
// 如果 x 處於等待態, promise 需保持為等待態直至 x 被執行或拒絕
if(x.status === PENDING) {
x.then(function(y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
} else if(x.status === FULFILLED) {
// 如果 x 處於執行態,用相同的值執行 promise
resolve(x.value);
} else if(x.status === REJECTED) {
// 如果 x 處於拒絕態,用相同的據因拒絕 promise
reject(x.reason);
}
}
// 如果 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) {
if(reason instanceof Error) {
throw reason;
} else {
throw new Error(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() {
try {
if(typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
});
that.onRejectedCallbacks.push(function() {
try {
if(typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
});
});
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。下面再回顧下幾個要點:
- Promise其實是一個發布訂閱模式
then方法對於還在pending的任務,其實是將回調函數onFilfilled和onRejected塞入了兩個數組- Promise構造函數里面的
resolve方法會將數組onFilfilledCallbacks里面的方法全部拿出來執行,這里面是之前then方法塞進去的成功回調 - 同理,Promise構造函數里面的
reject方法會將數組onRejectedCallbacks里面的方法全部拿出來執行,這里面是之前then方法塞進去的失敗回調 then方法會返回一個新的Promise以便執行鏈式調用catch和finally這些實例方法都必須返回一個新的Promise實例以便實現鏈式調用
文章的最后,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝嗇你的贊和GitHub小星星,你的支持是作者持續創作的動力。
歡迎關注我的公眾號進擊的大前端第一時間獲取高質量原創~
“前端進階知識”系列文章:https://juejin.im/post/5e3ffc85518825494e2772fd
“前端進階知識”系列文章源碼GitHub地址: https://github.com/dennis-jiang/Front-End-Knowledges

