Promise做爲異步編程的一種解決方案,比傳統的回調和事件更增強大,也是學習前端所必需要掌握的。做爲一個有追求的前端,不只要熟練掌握Promise的用法,並且要對其實現原理有必定的理解(說白了,就是面試裝逼必備)。雖然網上有不少Promise的實現代碼,幾百行的,但我的以爲,不對異步編程和Promise有必定的理解,那些代碼也就是一個板子而已(面試可不能敲板子)。首先默認讀者都對Promise對象比較熟悉了,而後將從前端最經常使用的設計模式:發佈-訂閱和觀察者模式的角度來一步一步的實現Promise。javascript
既然Promise是一種異步解決方案,那麼在沒有Promise對象以前是怎麼作異步處理的呢?有兩種方法:回調函數和發佈-訂閱或觀察者設計模式。(關於實現異步編程的更多方式請參考個人文章:JavaScript實現異步編程的5種方式)前端
相信回調函數讀者都不陌生,畢竟最先接觸的也就是回調函數了,並且用回調函數作異步處理也很簡單,以nodejs文件系統模塊fs爲例,讀取一個文件通常都會這麼作java
fs.readFile("h.js", (err, data) => {
console.log(data.toString())
});複製代碼
其缺點也很明顯,當異步流程變得複雜,那麼回調也會變得很複雜,有時也叫作」回調地獄」,就以文件複製爲例node
fs.exists("h.js", exists => { // 文件是否存在
if (exists) {
fs.readFile("h.js", (err, data) => { // 讀文件
fs.mkdir(__dirname + "/js/", err => { // 建立目錄
fs.writeFile(__dirname + "/js/h.js", data, err => { // 寫文件
console.log("複製成功,再回調下去,代碼真的很難看得懂")
})
});
});
}
});複製代碼
其實代碼仍是能閱讀的,感謝JS設計者沒有把函數的花括號給去掉。像沒有花括號的python寫回調就是(就是個笑話。不是說python很差,畢竟JavaScript是世界上最好的語言)python
# 這代碼屬實無法看啊
def callback_1():
# processing ...
def callback_2():
# processing.....
def callback_3():
# processing ....
def callback_4():
#processing .....
def callback_5():
# processing ......
async_function1(callback_5)
async_function2(callback_4)
async_function3(callback_3)
async_function4(callback_2)
async_function5(callback_1)複製代碼
第一次學設計模式仍是在學Java和C++的時候,畢竟設計模式就是基於面向對象,讓對象解耦而提出的。發佈訂閱設計模式和觀察者模式很像,可是有點細微的區別(面試考點來了)面試
觀察者模式 在軟件設計中是一個對象,維護一個依賴列表,當任何狀態發生改變自動通知它們。
發佈-訂閱模式是一種消息傳遞模式,消息的 發佈者 (Publishers)通常將消息發佈到特定消息中心, 訂閱者( Subscriber) 能夠按照本身的需求從消息中心訂閱信息,跟消息隊列挺相似的。
在觀察者模式只有兩種組件:接收者和發佈者,而發佈-訂閱模式中則有三種組件:發佈者、消息中心和接收者。編程
在代碼實現上的差別也比較明顯設計模式
觀察者設計模式promise
// 觀察者設計模式
class Observer {
constructor () {
this.observerList = [];
}
subscribe (observer) {
this.observerList.push(observer)
}
notifyAll (value) {
this.observerList.forEach(observe => observe(value))
}
}複製代碼
發佈-訂閱設計模式(nodejs EventEmitter)閉包
// 發佈訂閱
class EventEmitter {
constructor () {
this.eventChannel = {}; // 消息中心
}
// subscribe
on (event, callback) {
this.eventChannel[event] ? this.eventChannel[event].push(callback) : this.eventChannel[event] = [callback]
}
// publish
emit (event, ...args) {
this.eventChannel[event] && this.eventChannel[event].forEach(callback => callback(...args))
}
// remove event
remove (event) {
if (this.eventChannel[event]) {
delete this.eventChannel[event]
}
}
// once event
once (event, callback) {
this.on(event, (...args) => {
callback(...args);
this.remove(event)
})
}
}複製代碼
從代碼中也能看出他們的區別,觀察者模式不對事件進行分類,當有事件時,將通知全部觀察者。發佈-訂閱設計模式對事件進行了分類,觸發不一樣的事件,將通知不一樣的觀察者。因此能夠認爲後者就是前者的一個升級版,對通知事件作了更細粒度的劃分。
發佈-訂閱和觀察者在異步中的應用
// 觀察者
const observer = new Observer();
observer.subscribe(value => {
console.log("第一個觀察者,接收到的值爲:");
console.log(value)
});
observer.subscribe(value => {
console.log("第二個觀察者,接收到的值爲");
console.log(value)
});
fs.readFile("h.js", (err, data) => {
observer.notifyAll(data.toString())
});複製代碼
// 發佈-訂閱
const event = new EventEmitter();
event.on("err", console.log);
event.on("data", data => {
// do something
console.log(data)
});
fs.readFile("h.js", (err, data) => {
if (err) event.emit("err", err);
event.emit("data", data.toString())
});複製代碼
兩種設計模式在異步編程中,都是經過註冊全局觀察者或全局事件,而後在異步環境裏通知全部觀察者或觸發特定事件來實現異步編程。
劣勢也很明顯,好比全局觀察者/事件過多難以維護,事件名命衝突等等,所以Promise便誕生了。
Promise在必定程度上繼承了觀察者和發佈-訂閱設計模式的思想,咱們先從一段Promise代碼開始,來分析Promise是如何使用觀察者設計模式
const asyncReadFile = filename => new Promise((resolve) => {
fs.readFile(filename, (err, data) => {
resolve(data.toString()); // 發佈者 至關於觀察者模式的notifyAll(value) 或者發佈訂閱模式的emit
});
});
asyncReadFile("h.js").then(value => { // 訂閱者 至關於觀察者模式的subscribe(value => console.log(value)) 或者發佈訂閱模式的on
console.log(value);
});複製代碼
從上面的Promise代碼中,我以爲Promise方案優於前面的發佈-訂閱/觀察者方案的緣由就是:對異步任務的封裝,事件發佈者在回調函數裏(resolve),事件接收者在對象方法裏(then()),使用局部事件,對二者進行了更好的封裝,而不是扔在全局中。
基於上面的思想,咱們能夠實現一個簡單的Promise:MyPromise
class MyPromise {
constructor (run) { // run 函數 (resolve) => any
this.observerList = [];
const notifyAll = value => this.observerList.forEach(callback => callback(value));
run(notifyAll); // !!! 核心
}
subscribe (callback) {
this.observerList.push(callback);
}
}
//
const p = new MyPromise(notifyAll => {
fs.readFile("h.js", (err, data) => {
notifyAll(data.toString()) // resolve
})
});
p.subscribe(data => console.log(data)); // then
複製代碼
幾行代碼就實現了一個簡單的Promise,而上面的代碼也就是把觀察者設計模式稍微改了改而已。
固然還沒結束,上面的MyPromise是有問題的。以前說了Promise是對異步任務的封裝,能夠當作最小異步單元(像回調同樣),而異步結果也應該只有一個,即Promise中的resolve只能使用一次,至關於EventEmitter的once事件。而上面實現的MyPromise的notifyAll是能夠用屢次的(沒有爲何),所以這就能夠產生異步任務的結果能夠不止一個的錯誤。所以解決方法就是加一個bool變量或者添加狀態即pending態和fulfilled態(本質上和一個bool變量是同樣的),當notifyAll調用一次後立馬鎖住notifyAll或者當pending態變爲fulfilled態後再次調用notifyAll函數將不起做用。
爲了和Promise對象一致,這裏使用添加狀態的方式(順便把方法名給改了一下, notifyAll => resolve, subscribe => then)。
const pending = "pending";
const fulfilled = "fulfilled";
class MyPromise {
constructor (run) { // run 函數 (resolve) => any
this.observerList = [];
this.status = pending;
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.observerList.forEach(callback => callback(value));
}
};
run(resolve); // !!! 核心
}
then (callback) {
this.observerList.push(callback);
}
}
const p = new MyPromise(resolve => {
setTimeout(() => {
resolve("hello world");
resolve("hello world2"); // 很差使了
}, 1000);
});
p.then(value => console.log(value));複製代碼
貌似開始有點輪廓了,不過如今的MyPromise中的then可沒有鏈式調用,接下來咱們來實現then鏈,須要注意的在Promise中then方法返回的是一個新的Promise實例不是以前的Promise。因爲then方法一直返回新的MyPromise對象,因此須要一個屬性來保存惟一的異步結果。另外一方面,在實現then方法依然是註冊回調,但實現時須要考慮當前的狀態,若是是pending態,咱們須要在返回新的MyPromise的同時,將回調註冊到隊列中,若是是fulfilled態,那直接返回新的MyPromise對象,並將上一個MyPromise對象的結果給新的MyPromise對象。
const pending = "pending";
const fulfilled = "fulfilled";
class MyPromise {
constructor (run) { // run 函數 (resolve) => any
this.resolvedCallback = [];
this.status = pending;
this.data = void 666; // 保存異步結果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value; // 存一下結果
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
run(resolve); // !!! 核心
}
then (onResolved) {
// 這裏須要對onResolved作一下處理,當onResolved不是函數時將它變成函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
switch (this.status) {
case pending: {
return new MyPromise(resolve => {
this.resolvedCallback.push(value => { // 再包裝
const result = onResolved(value); // 須要判斷一下then接的回調返回的是否是一個MyPromise對象
if (result instanceof MyPromise) {
result.then(resolve) // 若是是,直接使用result.then後的結果,畢竟Promise裏面就須要這麼作
} else {
resolve(result); // 感覺一下閉包的偉大
}
})
})
}
case fulfilled: {
return new MyPromise(resolve => {
const result = onResolved(this.data); // fulfilled態,this.data必定存在,其實這裏就像map過程
if (result instanceof MyPromise) {
result.then(resolve)
} else {
resolve(result); // 閉包真偉大
}
})
}
}
}
}
const p = new MyPromise(resolve => {
setTimeout(() => {
resolve("hello world");
resolve("hello world2"); // 很差使了
}, 1000);
});
p.then(value => value + "dpf")
.then(value => value.toUpperCase())
.then(console.log);複製代碼
以上代碼須要重點理解,畢竟理解了上面的代碼,下面的就很容易了
只有resolve和then的MyPromise對象已經完成。沒有測試的庫就是耍流氓,沒有差錯處理的代碼也是耍流氓,因此錯誤處理仍是很重要的。因爲一個異步任務可能完不成或者中間會出錯,這種狀況必須得處理。所以咱們須要加一個狀態rejected來表示異步任務出錯,而且使用rejectedCallback隊列來存儲reject發送的錯誤事件。(前方高能預警,面向try/catch編程開始了)
const pending = "pending";
const fulfilled = "fulfilled";
const rejected = "rejected"; // 添加狀態 rejected
class MyPromise {
constructor (run) { // run 函數 (resolve, reject) => any
this.resolvedCallback = [];
this.rejectedCallback = []; // 添加一個處理錯誤的隊列
this.status = pending;
this.data = void 666; // 保存異步結果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value;
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
const reject = err => {
if (this.status === pending) {
this.status = rejected;
this.data = err;
this.rejectedCallback.forEach(callback => callback(this.data));
}
};
try { // 對構造器裏傳入的函數進行try / catch
run(resolve, reject); // !!! 核心
} catch (e) {
reject(e)
}
}
then (onResolved, onRejected) { // 添加兩個監聽函數
// 這裏須要對onResolved作一下處理,當onResolved不是函數時將它變成函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
switch (this.status) {
case pending: {
return new MyPromise((resolve, reject) => {
this.resolvedCallback.push(value => {
try { // 對整個onResolved進行try / catch
const result = onResolved(value);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e) // 捕獲異常,將異常發佈
}
});
this.rejectedCallback.push(err => {
try { // 對整個onRejected進行try / catch
const result = onRejected(err);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(err)
}
} catch (e) {
reject(err) // 捕獲異常,將異常發佈
}
})
})
}
case fulfilled: {
return new MyPromise((resolve, reject) => {
try { // 對整個過程進行try / catch
const result = onResolved(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e) // 捕獲異常,將異常發佈
}
})
}
case rejected: {
return new MyPromise((resolve, reject) => {
try { // 對整個過程進行try / catch
const result = onRejected(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (e) {
reject(e) // 捕獲異常,將異常發佈
}
})
}
}
}
}
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(new Error("error"));
resolve("hello world"); // 很差使了
resolve("hello world2"); // 很差使了
}, 1000);
});
p.then(value => value + "dpf")
.then(console.log)
.then(() => {}, err => console.log(err));複製代碼
能夠看出then方法的實現比較複雜,但這是一個核心的方法,實現了這個後面的其餘方法就很好實現了,下面給出MyPromise的每個方法的實現。
這個實現很是簡單
catch (onRejected) {
return this.then(void 666, onRejected)
}複製代碼
static resolve(p) {
if (p instanceof MyPromise) {
return p.then()
}
return new MyPromise((resolve, reject) => {
resolve(p)
})
}複製代碼
static reject(p) {
if (p instanceof MyPromise) {
return p.catch()
}
return new MyPromise((resolve, reject) => {
reject(p)
})
}
複製代碼
static all (promises) {
return new MyPromise((resolve, reject) => {
try {
let count = 0,
len = promises.length,
value = [];
for (let promise of promises) {
MyPromise.resolve(promise).then(v => {
count ++;
value.push(v);
if (count === len) {
resolve(value)
}
})
}
} catch (e) {
reject(e)
}
});
}複製代碼
static race(promises) {
return new MyPromise((resolve, reject) => {
try {
for (let promise of promises) {
MyPromise.resolve(promise).then(resolve)
}
} catch (e) {
reject(e)
}
})
}複製代碼
const pending = "pending";
const fulfilled = "fulfilled";
const rejected = "rejected"; // 添加狀態 rejected
class MyPromise {
constructor (run) { // run 函數 (resolve, reject) => any
this.resolvedCallback = [];
this.rejectedCallback = []; // 添加一個處理錯誤的隊列
this.status = pending;
this.data = void 666; // 保存異步結果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value;
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
const reject = err => {
if (this.status === pending) {
this.status = rejected;
this.data = err;
this.rejectedCallback.forEach(callback => callback(this.data));
}
};
try { // 對構造器裏傳入的函數進行try / catch
run(resolve, reject); // !!! 核心
} catch (e) {
reject(e)
}
}
static resolve (p) {
if (p instanceof MyPromise) {
return p.then()
}
return new MyPromise((resolve, reject) => {
resolve(p)
})
}
static reject (p) {
if (p instanceof MyPromise) {
return p.catch()
}
return new MyPromise((resolve, reject) => {
reject(p)
})
}
static all (promises) {
return new MyPromise((resolve, reject) => {
try {
let count = 0,
len = promises.length,
value = [];
for (let promise of promises) {
MyPromise.resolve(promise).then(v => {
count ++;
value.push(v);
if (count === len) {
resolve(value)
}
})
}
} catch (e) {
reject(e)
}
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
try {
for (let promise of promises) {
MyPromise.resolve(promise).then(resolve)
}
} catch (e) {
reject(e)
}
})
}
catch (onRejected) {
return this.then(void 666, onRejected)
}
then (onResolved, onRejected) { // 添加兩個監聽函數
// 這裏須要對onResolved作一下處理,當onResolved不是函數時將它變成函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
switch (this.status) {
case pending: {
return new MyPromise((resolve, reject) => {
this.resolvedCallback.push(value => {
try { // 對整個onResolved進行try / catch
const result = onResolved(value);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e)
}
});
this.rejectedCallback.push(err => {
try { // 對整個onRejected進行try / catch
const result = onRejected(err);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(err)
}
} catch (e) {
reject(err)
}
})
})
}
case fulfilled: {
return new MyPromise((resolve, reject) => {
try { // 對整個過程進行try / catch
const result = onResolved(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result); // emit
}
} catch (e) {
reject(e)
}
})
}
case rejected: {
return new MyPromise((resolve, reject) => {
try { // 對整個過程進行try / catch
const result = onRejected(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (e) {
reject(e)
}
})
}
}
}
}複製代碼
本文想要從發佈-訂閱和觀察者模式分析Promise的實現,先從異步編程的演變提及,回調函數到發佈-訂閱和觀察者設計模式,而後發現Promise和觀察者設計模式比較相似,因此先從這個角度分析了Promise的實現,固然Promise的功能遠不如此,因此本文分析了Promise的經常使用方法的實現原理。Promise的出現改變了傳統的異步編程方式,使JavaScript在進行異步編程時更加靈活,代碼更加可維護、可閱讀。因此做爲一個有追求的前端,必需要對Promise的實現有必定的理解。