手寫promise,瞭解一下(一)

風光好採光發表於2018-07-28

JavaScript是一種屬於網路的指令碼語言,已經被廣泛用於Web應用開發,為使用者提供更流暢美觀的瀏覽效果。

眾所周知,js的事件處理模型決定了它內部很多行為都是非同步的,最常見的如setTimeout、setInterval、我們通常的ajax,當然還有我們的事件,程式碼如:

dom.addEventListener('keydown', function(e){
   console.log(e); 
})
複製程式碼

這就是一段普通的鍵盤捕獲程式,這本身當然是沒什麼問題的。有問題的是隨著業務越來越複雜,我們需要不斷的藉助非同步的方式處理各種各樣的邏輯,然後程式碼就變成了這樣:

ajax('requestA', function(resA){
    //do sth
    ajax('requestB', function(resB){
        //do sth
        ajax('requestC', function(resC){
            //do sth
            ajax('requestD', function(resD){
                //do sth
                ajax('requestE', function(resE){
                    //do sth
                    ajax('requestF', function(resF){
                        //do sth
                        ajax('requestG', function(resG){
                            //do sth
                            ajax('requestH', function(resH){
                                //do sth
                            })
                        })
                    })
                })            
            })        
        })    
    })    
})
複製程式碼

Promise的出現就是為了解決這種回撥地獄。今天我們就來了解一下Promise~~

一 、初識Promise

什麼是promise?

Promise可能大家都不陌生,因為Promise規範已經出來好一段時間了,同時Promise也已經納入了ES6,而且高版本的chrome、firefox瀏覽器都已經原生實現了Promise,只不過和現如今流行的類Promise類庫相比少些API。

所謂Promise,字面上可以理解為“承諾”,就是說A呼叫B,B返回一個“承諾”給A,然後A就可以在寫計劃的時候這麼寫:當B返回結果給我的時候,A執行方案S1,反之如果B因為什麼原因沒有給到A想要的結果,那麼A執行應急方案S2,這樣一來,所有的潛在風險都在A的可控範圍之內了。

  我們從最基礎的用法開始一步一步模仿promise的實現。      Promise是通過new來呼叫,而且需要傳入回撥函式:

var promise = new Promise(function(resolve, reject){
    //do something
});
複製程式碼
所以Promise是必須是一個可以建構函式:
複製程式碼
function Promise(executor){
    executor();
}
複製程式碼

Promise規範如下: 一個promise可能有三種狀態:等待(pending)、已完成(fulfilled)、已拒絕(rejected)

function Promise(executor){
    var self = this;
    self.state = 'padding';//初始狀態為pending

    executor();
}
複製程式碼
我們會在new的回撥函式裡改變promise的狀態
複製程式碼
var promise = new Promise(function(resolve, reject){
    resolve();//把promise的狀態改為已完成
});
複製程式碼

我們的原始碼為

function Promise(executor){
    var self = this;
    self.state = 'padding';//初始狀態為pending
    
    function resolve(){
        self.state = 'resolved';
    }
    
    function reject(){
        self.state = 'rejected';
    }

    executor(resolve, reject);
}
複製程式碼

promise的狀態只可能從“等待”轉到“完成”態或者“拒絕”態,不能逆向轉換,同時“完成”態和“拒絕”態不能相互轉換

var promise = new Promise(function(resolve, reject){
    resolve();//把promise的狀態改為已完成
    reject();//把promise的狀態改為已拒絕
    //這樣會2此修改promise的狀態
});
複製程式碼

為了防止這種情況,我們修改我們的程式碼

function Promise(executor){
    var self = this;
    self.state = 'padding';//初始狀態為pending
    
    function resolve(){
        if(self.state == 'padding'){
            self.state = 'resolved';   
        }
    }
    
    function reject(){
        if(self.state == 'padding'){
            self.state = 'rejected';
        }
    }

    executor(resolve, reject);
}
複製程式碼

promise必須實現then方法,then方法接受兩個引數,第一個引數是成功時的回撥,在promise由“等待”態轉換到“完成”態時呼叫,另一個是失敗時的回撥,在promise由“等待”態轉換到“拒絕”態時呼叫。

var promise = new Promise(function(resolve, reject){
    resolve();//把promise的狀態改為已完成
});
promise.then(function(){
    console.log('promise的狀態改為已完成');//控制檯會列印此句
}, function(){
    console.log('promise的狀態改為已拒絕');
});
複製程式碼
var promise = new Promise(function(resolve, reject){
    reject();//把promise的狀態改為已拒絕
});
promise.then(function(){
    console.log('promise的狀態改為已完成');
}, function(){
    console.log('promise的狀態改為已拒絕');//控制檯會列印此句
});
複製程式碼

我們的原始碼為

function Promise(executor){
    var self = this;
    self.state = 'padding';//初始狀態為pending
    
    function resolve(){
        if(self.state == 'padding'){
            self.state = 'resolved';   
        }
    }
    
    function reject(){
        if(self.state == 'padding'){
            self.state = 'rejected';
        }
    }

    executor(resolve, reject);
}
Promise.prototype.then = function(onFulfilled, onRejected){
    var self = this;
    
    if(self.state = 'resolved'){
        onFulfilled();
    }
    
    if(self.state = 'rejected'){
        onRejected();
    }
}
複製程式碼

很多時候promise是非同步的,比如

let p = new Promise(function(resolve, reject){
	setTimeout(function(){
		resolve();
	}, 3000)
});
複製程式碼

為了解決非同步問題,我們的原始碼改為

function Promise(executor){
	let self = this;
	self.state = 'padding';
	self.onResolvedCallbacks = [];
	self.onRejectedCallbacks = [];

	function resolve(value){
		if(self.state == 'padding'){
			self.state = 'resolved'
			self.onResolvedCallbacks.forEach(fn=>fn());
		}	
	}

	function reject(reson){
		if(self.state == 'padding'){
			self.state = 'rejected'
			self.onRejectedCallbacks.forEach(fn=>fn());
		}
	}

	try{
		executor(resolve, reject);
	}catch(e){
		reject(e);
	}
}

Promise.prototype.then = function(onFulfilled, onRejected){
	let self = this;

	if(self.state === 'resolved'){
		onFulfilled();
	}

	if(self.state === 'rejected'){
		onRejected();
	}

	if(self.state === 'padding'){
		self.onResolvedCallbacks.push(function(){
			onFulfilled();
		});
		self.onRejectedCallbacks.push(function(){
			onRejected();
		});
	}
}
複製程式碼

promise規定執行then回撥時,能夠接收一個值,成功有成功的值,失敗有失敗的原因。

let p = new Promise(function(resolve, reject){
	setTimeout(function(){
		resolve('hello');
	}, 3000)
});

p.then(function(data){
	console.log(data);//列印出  hello
}, function(err){
	console.log(err);
});
複製程式碼

所以我們的原始碼改為

function Promise(executor){
	let self = this;
	self.state = 'padding';
	self.value = undefined;
	self.reson = undefined;
	self.onResolvedCallbacks = [];
	self.onRejectedCallbacks = [];

	function resolve(value){
		if(self.state == 'padding'){
			self.state = 'resolved'
			self.value = value
			self.onResolvedCallbacks.forEach(fn=>fn());
		}	
	}

	function reject(reson){
		if(self.state == 'padding'){
			self.state = 'rejected'
			self.reson = reson
			self.onRejectedCallbacks.forEach(fn=>fn());
		}
	}

	try{
		executor(resolve, reject);
	}catch(e){
		reject(e);
	}
}

Promise.prototype.then = function(onFulfilled, onRejected){
	let self = this;

	if(self.state === 'resolved'){
		onFulfilled(self.value);
	}

	if(self.state === 'rejected'){
		onRejected(self.reson);
	}

	if(self.state === 'padding'){
		self.onResolvedCallbacks.push(function(){
			onFulfilled(self.value);
		});
		self.onRejectedCallbacks.push(function(){
			onRejected(self.reson);
		});
	}
}
複製程式碼

下一片文章我們接著來解決鏈式呼叫的問題

相關文章