關於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 MyPromise {
constructor (executor) {
let self = this;
this.value = undefined;
// 回調函數,用來賦值給 value
function resolve(value){
self.value = value;
}
executor(resolve)
}
}
var a = new MyPromise(executor);
解釋一下上面的代碼:我們將 executor 傳入並立即執行,在 resolve 回調函數中我們能夠拿到 value 值,我們定義 resolve 回調函數將 value 的值賦給 this.value。
這樣我們就輕松的完成了 a 這個對象的賦值。由於是異步得到的,那么我們怎么用方法去處理這個數據呢?
根據函子的思想,在拿到數據之后,我們應該讓 map 里傳入的 fn 函數去處理數據。由於是異步處理, resolve 執行后才拿到數據,所以我們定義了一個 callback 函數,在 callback 里面執行 fn。最后把 fn 處理的結果交給下一個函子的 resolve 保存。
class MyPromise {
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 MyPromise((resolve) => {
self.callback = function(){
let data = fn(self.value)
resolve(data)
}
})
}
}
new MyPromise(executor).map(add1).map(add1)
同時調用同一個Promise函子
Promise除了能鏈式調用,還能同時調用,比如:
var a = new MyPromise(executor);
var b = a.map(add);
var c = a.map(minus);
像上面這個同時調用a這個函子。你會發現,它實際上只執行了c。原因也很簡單,b先給a的 callback 賦值,然后c又給a的 callback 賦值。所以把b給覆蓋掉了就不會執行啦。解決這個問題很簡單,我們只需要讓callback變成一個數組就解決了。
class MyPromise {
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的奇幻漂流
標准PromiseA+規范實現:這一次,徹底弄懂 Promise 原理