Javascript Promise讓程式碼更優雅
回撥函式真正的問題在於他剝奪了我們使用 return 和 throw 這些關鍵字的能力。而 Promise 很好地解決了這一切。
在非同步程式設計中,我們經常需要使用回撥函式,過多層級的回撥會使本來簡潔的程式碼變得深奧隱晦難明,使用promise能完美解決回撥巢狀問題,讓程式碼賞心悅目,還能實現更多強大的功能,比如現實網路程式設計中的同步功能等。
javascript裡的promise功能和使用都比較類似於java裡的javaRX,當然是簡化版,本文著重分析promise的核心原始碼,示例程式碼是將微信小程式中的websocket使用promise的用法。
promise使用
function webSocket() {
var connectPromise;
function connectWS(success, failed) {
wx.connectSocket({//連線伺服器
url:'wss://localhost:8443/examples/websocket/chat'
});
wx.onSocketOpen(function (res) {
console.log('WebSocket連線已開啟!', Date.now(), res)
socket.state.isConnected = true ;
success(); //連線成功,這裡回撥promise的resolve
});
wx.onSocketError(function (res) {
console.log('WebSocket連線開啟失敗,請檢查!', res)
socket.state.isConnected = false;
connectPromise = null;
failed(); //連線失敗,這裡回撥promise的rejected
});
wx.onSocketClose(function (res) {
console.log('WebSocket 已關閉!', Date.now())
socket.state.isConnected = false;
wx.connectSocket({ //websocket關閉馬上重連
url:'wss://localhost:8443/examples/websocket/chat'
});
});
wx.onSocketMessage(function (res) { //接收伺服器的資訊
console.log('WebSocket收到伺服器內容:', res.data);
});
}
function send(msg) { //傳送給伺服器資訊
wx.sendSocketMessage({
data: msg
});
}
var socket = {
state: {
isConnected: false,
},
connect: function () {
if (!connectPromise) {//連線請求只限制一個
//resolve,reject引數是必須的,用於通知promise回撥結果
connectPromise = new Promise(function (resolve, reject) {
if (!socket.state.isConnected) {
connectWS(resolve, reject); //連線伺服器
} else {
resolve()
}
});
}
return connectPromise;
},
request: function (data) {
send(data);
}
};
return socket;
}
上面程式碼很簡單,就是簡單封裝了下微信的websocket方法,在使用的時候用了一個promise,下面看看如何使用上面的封裝:
function webSocketRequest(data) {
//連線websocket伺服器,成功後,回撥用then裡面的方法
webSocket.connect().then(function () {
webSocket.request(data); //傳送資料
}
上面的程式碼是不是很優雅,並且能有效避免多次請求連線伺服器。
Promise 物件有以下兩個特點:
(1)物件的狀態不受外界影響。Promise 物件代表一個非同步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和 Rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是 Promise 這個名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise 物件的狀態改變,只有兩種可能:從 Pending 變為 Resolved 和從 Pending 變為 Rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對 Promise 物件添加回調函式,也會立即得到這個。
promise原始碼解析
先看看幾個後面用到的輔助函式:
//var asap = require('asap/raw');
var asap = setTimeout; //asap(as soon as possible)儘早執行的意思,
//小程式中改成setTimeout方法
function noop() { //空函式
}
// States:
//
// 0 - pending
// 1 - fulfilled with _value
// 2 - rejected with _value
// 3 - adopted the state of another promise, _value
var LAST_ERROR = null;
var IS_ERROR = {};
function getThen(obj) { //獲取then屬性
try {
return obj.then;
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
function tryCallOne(fn, a) {//呼叫fn方法,並將引數a傳給fn
try {
return fn(a);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
function tryCallTwo(fn, a, b) {//呼叫fn方法,並將引數a,b傳給fn
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
接著往下看:
module.exports = Promise;
function Promise(fn) {
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
if (typeof fn !== 'function') {//引數必須收function
throw new TypeError('Promise constructor\'s argument
is not a function');
}
this._deferredState = 0;//當前promise的事件處理函式狀態
this._state = 0;//promise的狀態,0表示進行中,1成功,2失敗
this._value = null;//儲存fn方法呼叫後的結果,這個結果會傳遞給事件處理函式
this._deferreds = null;//儲存當前promise的事件處理函式,可以多個
if (fn === noop) return;//為空,返回
doResolve(fn, this);
}
Promise._onHandle = null;
Promise._onReject = null;
Promise._noop = noop;
上面promise的建構函式很簡單,就是新建了一個promise物件,下面看看doResolve方法,在new一個promise的時候就會執行它:
function doResolve(fn, promise) {
var done = false;//標誌位,確保後面的resolve或reject方法只執行一次
//執行fn方法並傳入兩個function引數,
//這裡的兩個引數就是上面示例裡呼叫的resolve和reject引數
var res = tryCallTwo(fn, function (value) {
//value就是我們自己要傳給then裡事件處理方法的值
if (done) return;//如果已經執行過,不再執行,
done = true;//設定標誌位
resolve(promise, value); //執行resolve內部方法
}, function (reason) {
if (done) return;
done = true;
reject(promise, reason); //執行reject內部方法
});
if (!done && res === IS_ERROR) {//如果執行fn方法異常
done = true;
reject(promise, LAST_ERROR);//執行reject內部方法
}
}
上面程式碼裡看到了兩個新方法:resolve和reject,看看他們的程式碼:
function resolve(self, newValue) {
if (newValue === self) {//給事件處理方法的值不能是本身promise自己
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')
);
}
if (newValue && (typeof newValue === 'object'
|| typeof newValue === 'function')) {
var then = getThen(newValue);//取返回值裡面的then屬性
if (then === IS_ERROR) {//如果異常
return reject(self, LAST_ERROR);
}
//返回值是一個新的promise物件
if (then === self.then && newValue instanceof Promise) {
self._state = 3;//設定當前promise狀態為3
self._value = newValue; //儲存這個返回值
finale(self);
return;
} else if (typeof then === 'function') {
//如果then只是一個普通的function,再次執行doResolve
//傳入then方法,bind操作代表then的上下文是newValue物件
doResolve(then.bind(newValue), self);
return;
}
}
//普通返回值物件都會直接走這裡
self._state = 1; //設定狀態位1,代表執行成功,
self._value = newValue;//儲存返回值
finale(self);//執行finale,後面分析
}
function reject(self, newValue) {
self._state = 2;//設定狀態位2,代表執行失敗
self._value = newValue;//儲存返回值
if (Promise._onReject) {//null
Promise._onReject(self, newValue);
}
finale(self);//執行finale,後面分析
}
由上面分析可知,不管是resolve或是reject方法都會走finale方法:
function finale(self) {
if (self._deferredState === 1) {//只有一個事件處理函式
handle(self, self._deferreds);//處理事件
self._deferreds = null;
}
if (self._deferredState === 2) {//多個事件處理函式
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);//處理事件
}
self._deferreds = null;
}
}
上面經常提到事件處理函式,那麼這些事件處理函式從何而來?這就得看看promise的另一個重要方法了,那就是then方法,它就是用來新增事件處理函式的:
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
//構造一個新的promise,並且執行方法為空函式
//這樣實現可以鏈式連線多個promise,實現結果鏈式傳遞
var res = new Promise(noop);
//呼叫處理方法
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};
//構造一個Handler物件,儲存onFulfilled,onRejected和promise
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
前面finale方法和then方法都會進入handle方法,下面分析下它:
function handle(self, deferred) {
while (self._state === 3) {//當前promise返回的是一個新的promise
self = self._value; //將新的promise賦值給self
}
if (Promise._onHandle) { //null
Promise._onHandle(self);
}
if (self._state === 0) { //當前promise處於正在執行中
if (self._deferredState === 0) {//還沒有事件處理函式
self._deferredState = 1;
self._deferreds = deferred;//儲存事件處理函式
return;
}
if (self._deferredState === 1) {//已經有一個事件處理函數了
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];//變成資料
return;
}
//如果儲存處理函式是陣列,則直接push進陣列
self._deferreds.push(deferred);
return;
}
//promise處理執行完後,會進入這裡
handleResolved(self, deferred);
}
function handleResolved(self, deferred) {
asap(function () {//讓處理器儘快執行此方法
//根據處理結果獲取相應回撥onFulfilled or onRejected
var cb = self._state === 1 ? deferred.onFulfilled
: deferred.onRejected;
if (cb === null) {//如果當前promise事件處理為空
if (self._state === 1) {
//執行下一個promise的事件處理函式,實現鏈式傳遞
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
//執行相應事件處理函式
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {//如果執行出錯
//執行下一個promise的失敗事件處理函式,實現鏈式傳遞
reject(deferred.promise, LAST_ERROR);
} else {
//執行下一個promise的完成事件處理函式,實現鏈式傳遞
resolve(deferred.promise, ret);
}
});
}
至此promise的所有核心方法都已經完全分析完,javascript的promise就是將傳統的非同步回撥變成鏈式回撥處理,一種神奇程式設計思想,在特定場合能發揮強大的功能,比如登陸介面處理等。
完
更多精彩Android技術可以關注我們的微信公眾號,掃一掃下方的二維碼或搜尋關注公眾號:
Android老鳥