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老鸟
这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值