1. 程式人生 > >Javascript Promise讓程式碼更優雅

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老鳥
這裡寫圖片描述