本文首發于 vivo網際網路技術 微信公衆号
連結:
https://mp.weixin.qq.com/s/Xz2bGaLxVL4xw1M2hb2nJQ 作者:Morrain
很多同學在學習 Promise 時,知其然卻不知其是以然,對其中的用法了解不了。本系列文章由淺入深逐漸實作 Promise,并結合流程圖、執行個體以及動畫進行示範,達到深刻了解 Promise 用法的目的。
本系列文章有如下幾個章節組成:
- 圖解 Promise 實作原理(一)—— 基礎實作
- 圖解 Promise 實作原理(二)—— Promise 鍊式調用
- 圖解 Promise 實作原理(三)—— Promise 原型方法實作
- 圖解 Promise 實作原理(四)—— Promise 靜态方法實作
一、前言
上一節中,實作了 Promise 的基礎版本:
//極簡的實作+鍊式調用+延遲機制+狀态
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));
}
}
但鍊式調用,隻是在 then 方法中 return 了 this,使得 Promise 執行個體可以多次調用 then 方法,但因為是同一個執行個體,調用再多次 then 也隻能傳回相同的一個結果,通常我們希望的鍊式調用是這樣的:
//使用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);
}).then(function (name) {
//do something
return getCourseByName(name);
}).then(function (course) {
//do something
return getCourseDetailByCourse(course);
}).then(function (courseDetail) {
//do something
});
每個 then 注冊的 onFulfilled 都傳回了不同的結果,層層遞進,很明顯在 then 方法中 return this 不能達到這個效果。引入真正的鍊式調用,then 傳回的一定是一個新的Promise執行個體。

真正的鍊式 Promise 是指在目前 Promise 達到 fulfilled 狀态後,即開始進行下一個 Promise(後鄰 Promise)。那麼我們如何銜接目前 Promise 和後鄰 Promise 呢?(這是了解 Promise 的難點,我們會通過動畫示範這個過程)。
二、鍊式調用的實作
先看下實作源碼:
//完整的實作
class Promise {
callbacks = [];
state = 'pending';//增加狀态
value = null;//儲存結果
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
return new Promise(resolve => {
this._handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
}
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
//如果then中沒有傳遞任何東西
if (!callback.onFulfilled) {
callback.resolve(this.value);
return;
}
var ret = callback.onFulfilled(this.value);
callback.resolve(ret);
}
_resolve(value) {
this.state = 'fulfilled';//改變狀态
this.value = value;//儲存結果
this.callbacks.forEach(callback => this._handle(callback));
}
}
由上面的實作,我們可以看到:
- then 方法中,建立并傳回了新的 Promise 執行個體,這是串行Promise的基礎,是實作真正鍊式調用的根本。
- then 方法傳入的形參 onFulfilled 以及建立新 Promise 執行個體時傳入的 resolve 放在一起,被push到目前 Promise 的 callbacks 隊列中,這是銜接目前 Promise 和後鄰 Promise 的關鍵所在。
- 根據規範,onFulfilled 是可以為空的,為空時不調用 onFulfilled。
看下動畫示範:
(Promise 鍊式調用示範動畫)
當第一個 Promise 成功時,resolve 方法将其狀态置為 fulfilled ,并儲存 resolve 帶過來的value。然後取出 callbacks 中的對象,執行目前 Promise的 onFulfilled,傳回值通過調用第二個 Promise 的 resolve 方法,傳遞給第二個 Promise。動畫示範如下:
(Promise 鍊式調用 fulfilled)
為了真實的看到鍊式調用的過程,我寫一個mockAjax函數,用來模拟異步請求:
/**
* 模拟異步請求
* @param {*} url 請求的URL
* @param {*} s 指定該請求的耗時,即多久之後請求會傳回。機關秒
* @param {*} callback 請求傳回後的回調函數
*/
const mockAjax = (url, s, callback) => {
setTimeout(() => {
callback(url + '異步請求耗時' + s + '秒');
}, 1000 * s)
}
除此之外,我給 Promise 的源碼加上了日志輸出并增加了構造順序辨別,可以清楚的看到構造以及執行過程:
//Demo1
new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
}).then(result => {
console.log(result);
})
【
Demo1 的源碼】
執行結果如下:
[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId異步請求耗時1秒
[Promse-1]:_handle state= fulfilled
getUserId異步請求耗時1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined
通過列印出來的日志,可以看到:
- 構造 Promise-1 執行個體,立即執行 mackAjax('getUserId',callback);
- 調用 Promise-1 的 then 方法,注冊 Promise-1 的 onFulfilled 函數。
- then 函數内部構造了一個新的 Promise執行個體:Promise-2。立即執行 Promise-1 的 _handle方法。
- 此時 Promise-1 還是pending的狀态。
- Promise-1._handle 中就把注冊在 Promise-1 的 onFulfilled 和 Promise-2 的 resolve 儲存在 Promise-1 内部的 callbacks。
- 至此目前線程執行結束。傳回的是 Promise-2 的 Promise執行個體。
- 1s後,異步請求傳回,要改變 Promise-1 的狀态和結果,執行 resolve(result)。
- Promise-1 的值被改變,内容為異步請求傳回的結果:"getUserId異步請求耗時1s"。
- Promise-1 的狀态變成 fulfilled。
- Promise-1 的 onFulfilled 被執行,列印出了"getUserId異步請求耗時1秒"。
- 然後再調用 Promise-2.resolve。
- 改變 Promise-2 的值和狀态,因為 Promise-1 的 onFulfilled 沒有傳回值,是以 Promise-2的值為undefined。
上例中,如果把異步的請求改成同步會是什麼的效果?
new Promise(resolve => {
resolve('getUserId同步請求');
}).then(result => {
console.log(result);
});
//列印日志
[Promse-1]:constructor
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId同步請求
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= fulfilled
getUserId同步請求
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined
=> Promise {
callbacks: [],
name: 'Promse-2',
state: 'fulfilled',
value: undefined }
感興趣的可以自己去分析一下。
三、鍊式調用真正的意義
執行目前 Promise 的 onFulfilled 時,傳回值通過調用第二個 Promise 的 resolve 方法,傳遞給第二個 Promise,作為第二個 Promise 的值。于是我們考慮如下Demo:
//Demo2
new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
}).then(result => {
console.log(result);
//對result進行第一層加工
let exResult = '字首:' + result;
return exResult;
}).then(exResult => {
console.log(exResult);
});
Demo2 的源碼 我們加了一層 then,來看下執行的結果:
[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:then
[Promse-3]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-3', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId異步請求耗時1秒
[Promse-1]:_handle state= fulfilled
getUserId異步請求耗時1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= 字首:getUserId異步請求耗時1秒
[Promse-2]:_handle state= fulfilled
字首:getUserId異步請求耗時1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= undefined:
鍊式調用可以無限的寫下去,上一級 onFulfilled return 的值,會變成下一級 onFulfilled 的結果。可以參考Demo3:
Demo3 的源碼我們很容易發現,上述 Demo3 中隻有第一個是異步請求,後面都是同步的,我們完全沒有必要這麼鍊式的實作。如下一樣能得到我們想要的三個結果: 分别列印出來的值。
//等價于 Demo3
new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
}).then(result => {
console.log(result);
//對result進行第一層加工
let exResult = '字首:' + result;
console.log(exResult);
let finalResult = exResult + ':字尾';
console.log(finalResult);
});
那鍊式調用真正的意義在哪裡呢?
剛才示範的都是 onFulfilled 傳回值是 value 的情況,如果是一個 Promise 呢?是不是就可以通過 onFulfilled,由使用 Promise 的開發者決定後續 Promise 的狀态。
于是在 _resolve 中增加對前一個 Promise onFulfilled 傳回值的判斷:
_resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then === 'function') {
then.call(value, this._resolve.bind(this));
return;
}
}
this.state = 'fulfilled';//改變狀态
this.value = value;//儲存結果
this.callbacks.forEach(callback => this._handle(callback));
}
從代碼上看,它是對 resolve 中的值作了一個特殊的判斷,判斷 resolve 的值是否為 Promise執行個體,如果是 Promise 執行個體,那麼就把目前 Promise 執行個體的狀态改變接口重新注冊到 resolve 的值對應的 Promise 的 onFulfilled 中,也就是說目前 Promise 執行個體的狀态要依賴 resolve 的值的 Promise 執行個體的狀态。
//Demo4
const pUserId = new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
})
const pUserName = new Promise(resolve => {
mockAjax('getUserName', 2, function (result) {
resolve(result);
})
})
pUserId.then(id => {
console.log(id)
return pUserName
}).then(name => {
console.log(name)
})
Demo 4 的源碼 執行的結果如下:
[Promse-1]:constructor
[Promse-2]:constructor
[Promse-1]:then
[Promse-3]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-3]:then
[Promse-4]:constructor
[Promse-3]:_handle state= pending
[Promse-3]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-4', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId異步請求耗時1秒
[Promse-1]:_handle state= fulfilled
getUserId異步請求耗時1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-2]:then
[Promse-5]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:_resolve
[Promse-2]:_resolve value= getUserName異步請求耗時2秒
[Promse-2]:_handle state= fulfilled
[Promse-3]:_resolve
[Promse-3]:_resolve value= getUserName異步請求耗時2秒
[Promse-3]:_handle state= fulfilled
getUserName異步請求耗時2秒
[Promse-4]:_resolve
[Promse-4]:_resolve value= undefined
[Promse-5]:_resolve
[Promse-5]:_resolve value= undefined
一樣的,我做了一個示範動畫,還原了這個過程:
(Promise 真正的鍊式調用)
至此,就實作了 Promise 鍊式調用的全部内容。鍊式調用是 Promise 難點,更是重點。一定要通過執行個體還有動畫,深刻體會。下一節介紹 Promise 其它原型方法的實作。