這一次,徹底理解Promise原始碼思想
關於Promise的原始碼實現,網上有太多答案,我也看過很多資料,但都不是很明白。直到有一天我學完函數語言程式設計之函子的概念,才對Promise原始碼有了更深刻的認識。今天,就讓我們來重新認識一下Promise。
我們知道,Promise的誕生是為了解決“回撥地獄”的問題,它用同步鏈式的方式去解決非同步的巢狀回撥。
啥?同步鏈式?這不就是我們上一節學習的函子的思想嗎?如果對函子有所瞭解,那麼再來學習Promise原始碼就比較容易理解了。接下來,我們探究一下函子和Promise有著怎樣的關係。
實現一個簡單的Promise函子
先來回顧一下函子Functor的鏈式呼叫:
class Functor{ constructor (value) { this.value = value ; } map (fn) { return Functor.of(fn(this.value)) } } Functor.of = function (val) { return new Functor(val); } Functor.of(100).map(add1).map(add1).map(minus10) // var a = Functor.of(100); // var b = a.map(add1); // var c = b.map(add1); // var d = c.map(minus10);
函子的核心就是:每個函子Functor都是一個新的物件,這個物件的原型鏈上有 map 函式。通過 map 中傳遞進去的函式fn去處理函子儲存的資料,用得到的值去生成新的函子。
等等...函子是同步鏈式,而Promise是非同步鏈式。也就是說上面a的值是非同步產生的,那我們該何如傳入 this.value 值呢?
function executor(resolve){
setTimeout(()=>{ resolve(100) },500)
}
我們模擬一下通過 setTimeout500 毫秒後拿到資料100。其實也很簡單,我們可以傳進去一個 resolve 回撥函式去處理這個資料。
class Functor { constructor (executor) { let self = this; this.value = undefined; // 回撥函式,用來賦值給 value function resolve(value){ self.value = value; } executor(resolve) } } var a = new Functor(executor);
解釋一下上面的程式碼:我們將 executor 傳入並立即執行,在 resolve 回撥函式中我們能夠拿到 value 值,我們定義 resolve 回撥函式將 value 的值賦給 this.value。
這樣我們就輕鬆的完成了 a 這個物件的賦值。由於是非同步得到的,那麼我們怎麼用方法去處理這個資料呢?
根據函子的思想,在拿到資料之後,我們應該讓 map 裡傳入的 fn 函式去處理資料。由於是非同步處理, resolve 執行後才拿到資料,所以我們定義了一個 callback 函式,在 callback 裡面執行 fn。最後把 fn 處理的結果交給下一個函子的 resolve 儲存。
class Functor { constructor (executor) { let self = this; this.value = undefined; this.callback = null; // 回撥函式,用來賦值給 value function resolve(value){ self.value = value self.callback() // 得到 value 之後,在 callback 裡面執行 map 傳入的 fn 函式處理資料 } executor(resolve) } map (fn) { let self = this; return new Functor((resolve) => { self.callback = function(){ let data = fn(self.value) resolve(data) } }) } } new Functor(executor).map(add1).map(add1)
同時呼叫同一個Promise函子
Promise除了能鏈式呼叫,還能同時呼叫,比如:
var a = new Functor(executor);
var b = a.map(add);
var c = a.map(minus);
像上面這個同時呼叫a這個函子。你會發現,它實際上只執行了c。原因也很簡單,b先給a的 callback 賦值,然後c又給a的 callback 賦值。所以把b給覆蓋掉了就不會執行啦。解決這個問題很簡單,我們只需要讓callback變成一個數組就解決了。
class Functor {
constructor (executor) {
let self = this;
this.value = undefined;
this.callbacks = [];
function resolve(value){
self.value = value;
self.callbacks.forEach(item => item())
}
executor(resolve)
}
then (fn) {
return new MyPromise((resolve) => {
this.callbacks.push (()=>{
let data = fn(this.value)
console.log(data)
resolve(data)
})
})
}
}
var a = new MyPromise(executor);
var b = a.then(add).then(minus);
var c = a.then(minus);
我們定義了callbacks陣列,每次的呼叫a的then方法時。都將其存到callbacks陣列中。
當回撥函式拿到值時,在resolve中遍歷執行每個函式。
如果callbacks是空,forEach就不會執行,這也解決了之前把錯的問題
然後我們進一步改了函子的名字為 MyPromise,將map改成then
簡化了return中,let self = this;
增加reject回撥函式
我們都知道,在非同步呼叫的時候,我們往往不能拿到資料,返回一個錯誤的資訊。這一小節,我們對錯誤進行處理。
class MyPromise {
constructor (executor) {
let self = this;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value){
self.value = value;
self.onResolvedCallbacks.forEach(item => item())
}
function reject(reason){
self.reason = reason;
self.onRejectedCallbacks.forEach(item => item());
}
executor(resolve, reject);
}
then (fn,fn2) {
return new MyPromise((resolve,reject) => {
this.onResolvedCallbacks.push (()=>{
let data = fn(this.value)
console.log(data)
resolve(data)
})
this.onRejectedCallbacks.push (()=>{
let reason = fn2(this.reason)
console.log(reason)
reject(reason)
})
})
}
}
其實很簡單,就是我們就是在 executor 多傳遞進去一個 reject
根據非同步執行的結果去判斷執行 resolve,還是 reject
然後我們在 MyPromise 為 reject 定義出和 resolve 同樣的方法
然後我們在 then 的時候應該傳進去兩個引數,fn,fn2
這時候將executor函式封裝到asyncReadFile非同步讀取檔案的函式
function asyncReadFile(url){
return new MyPromise((resolve,reject) => {
fs.readFile(url, (err, data) => {
if(err){
console.log(err)
reject(err)
}else {
resolve(data)
}
})
})
}
var a = asyncReadFile('./data.txt');
a.then(add,mismanage).then(minus,mismanage);
這就是我們平時封裝非同步Promise函式的過程,這個過程有沒有覺得在哪見過。仔細看下,asyncReadFile 不就是前面我們提到的柯里化。
增加Promise狀態
我們定義進行中的狀態為pending
已成功執行後為fulfilled
失敗為rejected
class MyPromise {
constructor (executor) {
let self = this;
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value){
if (self.status === 'pending') {
self.status = 'fulfilled';
self.value = value;
self.onResolvedCallbacks.forEach(item => item())
}
}
function reject(reason){
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(item => item());
}
}
executor(resolve, reject);
}
then (fn,fn2) {
return new MyPromise((resolve,reject) => {
if(this.status === 'pending'){
this.onResolvedCallbacks.push (()=>{
let data = fn(this.value)
console.log(data)
resolve(data)
})
this.onRejectedCallbacks.push (()=>{
let reason = fn2(this.reason)
console.log(reason)
reject(reason)
})
}
if(this.status === 'fulfilled'){
let x = fn(this.value)
resolve(x)
}
if(this.status === 'rejected'){
let x = fn2(this.value)
reject(x)
}
})
}
}
var a = asyncReadFile('./data.txt');
a.then(add,mismanage).then(add,mismanage).then(add,mismanage);
最後,現在來看傳進去的方法 fn(this.value) ,我們需要用上篇講的Maybe函子去過濾一下。
Maybe函子優化
then (onResolved,onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}
return new MyPromise((resolve,reject) => {
if(this.status === 'pending'){
this.onResolvedCallbacks.push (()=>{
let x = onResolved(this.value)
resolve(x)
})
this.onRejectedCallbacks.push (()=>{
let x = onRejected(this.reason)
reject(x)
})
}
if(this.status === 'fulfilled'){
let x = onResolved(this.value)
resolve(x)
}
if(this.status === 'rejected'){
let x = onRejected(this.value)
reject(x)
}
})
}
Maybe函子很簡單,對onResolved和onRejected進行一下過濾。
總結
Promise是一個很不好理解的概念,但總歸核心思想還是函子。
同時,在函子的基礎上增加了一些非同步的實現。非同步的實現是一個比較費腦細胞的點,把加粗的字型花點時間多思考思考,加油!
參考連結:函數語言程式設計之Promise的奇幻漂