回调函数真正的问题在于他剥夺了我们使用 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老鸟