本文首發於 vivo互聯網技術 微信公衆號
連接: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ
做者:孔垂亮es6
不少同窗在學習 Promise 時,知其然殊不知其因此然,對其中的用法理解不了。本系列文章由淺入深逐步實現 Promise,並結合流程圖、實例以及動畫進行演示,達到深入理解 Promise 用法的目的。數組
本系列文章有以下幾個章節組成:promise
圖解 Promise 實現原理(一)—— 基礎實現bash
圖解 Promise 實現原理(二)—— Promise 鏈式調用微信
圖解 Promise 實現原理(三)—— Promise 原型方法實現異步
圖解 Promise 實現原理(四)—— Promise 靜態方法實現函數
本文適合對 Promise 的用法有所瞭解的人閱讀,若是還不清楚,請自行查閱阮一峯老師的 《ES6入門 之 Promise 對象》。學習
Promise 規範有不少,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升級版 Promise/A+,有興趣的能夠去了解下,最終 ES6 中採用了 Promise/A+ 規範。因此本文的Promise源碼是按照Promise/A+規範來編寫的(不想看英文版的移步Promise/A+規範中文翻譯)。動畫
爲了讓你們更容易理解,咱們從一個場景開始,一步一步跟着思路思考,會更容易看懂。ui
考慮下面一種獲取用戶 id 的請求處理:
//不使用Promise
http.get('some_url', function (result) {
//do something
console.log(result.id);
});
//使用Promise
new Promise(function (resolve) {
//異步請求
http.get('some_url', function (result) {
resolve(result.id)
})
}).then(function (id) {
//do something
console.log(id);
})複製代碼
//不使用Promise
http.get('some_url', function (id) {
//do something
http.get('getNameById', id, function (name) {
//do something
http.get('getCourseByName', name, function (course) {
//dong something
http.get('getCourseDetailByCourse', function (courseDetail) {
//do something
})
})
})
});
//使用Promise
function getUserId(url) {
return new Promise(function (resolve) {
//異步請求
http.get(url, function (id) {
resolve(id)
})
})
}
getUserId('some_url').then(function (id) {
//do something
return getNameById(id); // getNameById 是和 getUserId 同樣的Promise封裝。下同
}).then(function (name) {
//do something
return getCourseByName(name);
}).then(function (course) {
//do something
return getCourseDetailByCourse(course);
}).then(function (courseDetail) {
//do something
});複製代碼
說到底,Promise 也仍是使用回調函數,只不過是把回調封裝在了內部,使用上一直經過 then 方法的鏈式調用,使得多層的回調嵌套看起來變成了同一層的,書寫上以及理解上會更直觀和簡潔一些。
//極簡的實現
class Promise {
callbacks = [];
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
}
_resolve(value) {
this.callbacks.forEach(fn => fn(value));
}
}
//Promise應用
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
}).then((tip) => {
console.log(tip);
})複製代碼
調用 then 方法,將想要在 Promise 異步操做成功時執行的 onFulfilled 放入callbacks隊列,其實也就是註冊回調函數,能夠向觀察者模式方向思考;
建立 Promise 實例時傳入的函數會被賦予一個函數類型的參數,即 resolve,它接收一個參數 value,表明異步操做返回的結果,當異步操做執行成功後,會調用resolve方法,這時候其實真正執行的操做是將 callbacks 隊列中的回調一一執行。
(圖:基礎版本實現原理)
首先 new Promise 時,傳給 Promise 的函數設置定時器模擬異步的場景,接着調用 Promise 對象的 then 方法註冊異步操做完成後的 onFulfilled,最後當異步操做完成時,調用 resolve(value), 執行 then 方法註冊的 onFulfilled。
then 方法註冊的 onFulfilled 是存在一個數組中,可見 then 方法能夠調用屢次,註冊的多個onFulfilled 會在異步操做完成後根據添加的順序依次執行。以下:
//then 的說明
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
});
p.then(tip => {
console.log('then1', tip);
});
p.then(tip => {
console.log('then2', tip);
});複製代碼
//極簡的實現+鏈式調用
class Promise {
callbacks = [];
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
return this;//看這裏
}
_resolve(value) {
this.callbacks.forEach(fn => fn(value));
}
}
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
}).then(tip => {
console.log('then1', tip);
}).then(tip => {
console.log('then2', tip);
});複製代碼
(圖:基礎版本的鏈式調用)
上面 Promise 的實現存在一個問題:若是在 then 方法註冊 onFulfilled 以前,resolve 就執行了,onFulfilled 就不會執行到了。好比上面的例子中咱們把 setTimout 去掉:
//同步執行了resolve
let p = new Promise(resolve => {
console.log('同步執行');
resolve('同步執行');
}).then(tip => {
console.log('then1', tip);
}).then(tip => {
console.log('then2', tip);
});複製代碼
這顯然是不容許的,Promises/A+規範明確要求回調須要經過異步方式執行,用以保證一致可靠的執行順序。所以要加入一些處理,保證在 resolve 執行以前,then 方法已經註冊完全部的回調:
//極簡的實現+鏈式調用+延遲機制
class Promise {
callbacks = [];
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
return this;
}
_resolve(value) {
setTimeout(() => {//看這裏
this.callbacks.forEach(fn => fn(value));
});
}
}複製代碼
(圖:延遲機制)
可是這樣依然存在問題,在 resolve 執行後,再經過 then 註冊上來的 onFulfilled 都沒有機會執行了。以下所示,咱們加了延遲後,then1 和 then2 能夠打印出來了,但下例中的 then3 依然打印不出來。因此咱們須要增長狀態,而且保存 resolve 的值。
let p = new Promise(resolve => {
console.log('同步執行');
resolve('同步執行');
}).then(tip => {
console.log('then1', tip);
}).then(tip => {
console.log('then2', tip);
});
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});複製代碼
爲了解決上一節拋出的問題,咱們必須加入狀態機制,也就是你們熟知的 pending、fulfilled、rejected。
Promises/A+ 規範中明確規定了,pending 能夠轉化爲 fulfilled 或 rejected 而且只能轉化一次,也就是說若是 pending 轉化到 fulfilled 狀態,那麼就不能再轉化到 rejected。而且 fulfilled 和 rejected 狀態只能由 pending 轉化而來,二者之間不能互相轉換。
增長狀態後的實現是這樣的
//極簡的實現+鏈式調用+延遲機制+狀態
class Promise {
callbacks = [];
state = 'pending';//增長狀態
value = null;//保存結果
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
if (this.state === 'pending') {//在resolve以前,跟以前邏輯同樣,添加到callbacks中
this.callbacks.push(onFulfilled);
} else {//在resolve以後,直接執行回調,返回結果了
onFulfilled(this.value);
}
return this;
}
_resolve(value) {
this.state = 'fulfilled';//改變狀態
this.value = value;//保存結果
this.callbacks.forEach(fn => fn(value));
}
}複製代碼
注意:當增長完狀態以後,原先的_resolve中的定時器能夠去掉了。當reolve同步執行時,雖然callbacks爲空,回調函數尚未註冊上來,但沒有關係,由於後面註冊上來時,判斷狀態爲fulfilled,會當即執行回調。
(圖:Promise 狀態管理)
實現源碼中只增長了 fulfilled 的狀態 和 onFulfilled 的回調,但爲了完整性,在示意圖中增長了 rejected 和 onRejected 。後面章節會實現。
resolve 執行時,會將狀態設置爲 fulfilled ,並把 value 的值存起來,在此以後調用 then 添加的新回調,都會當即執行,直接返回保存的value值。
(Promise 狀態變化演示動畫)
詳情請點擊: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ
至此,一個初具功能的Promise就實現好了,它實現了 then,實現了鏈式調用,實現了狀態管理等等。但仔細想一想,鏈式調用的實現只是在 then 中 return 了 this,由於是同一個實例,調用再屢次 then 也只能返回相同的一個結果,這顯然是不能知足咱們的要求的。下一節,講述如何實現真正的鏈式調用。
更多內容敬請關注 vivo 互聯網技術 微信公衆號
注:轉載文章請先與微信號:labs2020 聯繫。