一、什么是promise/deferred 模式
promise/deferred 模式是,根据promise/A 或者它的增强修改版promise/A+ 规范 实现的promise异步操作的一种实现方式。
异步的广度使用使得回调,嵌套出现,但是一但出现深度的嵌套,就会让coding的体验变得相当不愉快,而且代码后期的维护也是相当吃力的。promise/deferred模式的出现,会在一定程度上缓解这个问题。接下来我会根据promise/a 规范来介绍promise/deferred模式。
(题外话:什么是规范,规范其实就相当于制定的规则,但却没有在代码层面上有默认的具体实现)
二、promise/a
promise/a 提议对单个异步操作作出了这样的抽象定义:
1.promise操作只会在以下3种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
2.promise的状态只会出现从等待状态向执行态或者拒绝态转化,不可以逆转。执行态和拒绝态之间不能相互转换
3.promise状态一旦被转换,就不能被更改。
4.在api上,规范定义比较简单,只要求promise 必修提供有一个then方法,以访问当前值、最终值和拒绝原因
then方法接受两个参数
promise.then(onFulfilled,onRejected)
5.then方法的onFulfilled,onRejected 方法都是可选参数,且不是function,都被忽略
6.then()方法返回promise对象,以实现链式写法。
三、promise/deferred模式
promise/deferred 模式 其实包含两部分:Promise 和 Deferred。
Deferred主要是用于内部,来维护异步模型的状态。
Promise只要用于外部,通过then()方法,暴露给外部调用,以添加业务逻辑和业务的组装。
promise 和 deferred的关系图
从图中可以看到:
1.deferred对象通过resolve方法,改变自身状态为执行态,并触发then()方法的onfulfilled回调函数
2.deferred对象通过reject方法,改变自身状态为拒绝态,并触发then()方法的onrejected回调函数
下面 我们就用代码来实现一下:
/**
* Promise 类
* @constructor
*/
function Promise() {
this.handler = {};
}
/**
* promise 对象的then方法
* @param onFulfilled 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值,其调用次数不可超过一次
* @param onRejected 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因,其调用次数不可超过一次
* @returns {Promise} 规范定义必修返回 primise对象
*/
Promise.prototype.then = function (onFulfilled, onRejected) {
var handler = {}
if (typeof onFulfilled === 'function') {
handler.resolve = onFulfilled
}
if (typeof onRejected === 'function') {
handler.reject = onRejected
}
this.handler = handler
return this
}
这里可以看到then方法所做的事情就是讲回调函数存放起来,为了完成整个流程,还需要触发执行这些回调函数的地方,而实现这些功能的对象就叫做deferred(延迟对象)。示范代码如下
function Deferred() {
/* 状态:默认 等待态 pending */
this.state = 'pending';
this.promise = new Promise()
}
Deferred.prototype.resolve = function (obj) {
this.state = 'fulfilled'
var handler = this.promise.handler
if (handler && handler.resolve) {
handler.resolve(obj)
}
}
Deferred.prototype.reject = function (obj) {
this.state = 'rejected'
var handler = this.promise.handler
if (handler && handler.reject) {
handler.reject(obj)
}
}
以上已经定义好了Promies 和Deferred ,那我们怎么对一个异步操作函数进行封装呢?
假如我们有这样的异步函数
function asyncDoSomeing(flag, message) {
setTimeout(function () {
if (flag) {
return message
}
}, 3000)
}
对其封装的代码就是
function asyncDoSomeing(flag, message) {
var deferred = new Deferred()
setTimeout(function () {
if (flag) {
deferred.resolve(message)
} else {
deferred.reject({code: 400, message: '拒绝'})
}
}, 3000)
return deferred.promise
}
最后我们就可以这么使用了
asyncDoSomeing(true, '测试执行成功').then(function (message) {
console.log(message)
}, function (err) {
console.log(err)
})
到这里只是单个promise对象的简单异步的操作控制,但是有熟悉node.js 和angular.js 的同学就会发现,这个写法跟node.js 里面的一个异步控制流程 q 模块(https://github.com/kriskowal/q )写法是一样的。是的哦 它就是promise/deferred 模式。随便提一下 Angularjs的$q对象是q的精简版。
四、链式调用
做到以上的简单实现,理想的coding方式,应该前一个调用结果作为下一个调用的输入,这就是链式调用。
为了避免回调地狱,可以借鉴jquery的链式写法。
$('#tab').eq($(this).index()).show().siblings().hide();
链式写法的核心在于,每个方法都返回 自身 this。
我们现在需要实现promise的链式调用,前一个调用结果作为下一个调用的输入
step1.then(step2).then(step3)
现在我们实现的then方法确实是返回this的,也就是promise本身,是可以实现链式的。
但是前一个调用的结果却做不到是下一个调用的输入
下面来改造一下上面的代码,让他实现这个要求。
function Promise() {
this.handlerQueue = [];
this.isPromise = true
}
1.将原本的handler对象改为 一个数组,存放所有then方法的回调。
Promise.prototype.then = function (onFulfilled, onRejected) {
var handler = {}
if (typeof onFulfilled === 'function') {
handler.resolve = onFulfilled
}
if (typeof onRejected === 'function') {
handler.reject = onRejected
}
this.handlerQueue.push(handler)
return this
}
function Deferred() {
this.state = 'pending'
this.promise = new Promise()
}
Deferred.prototype.resolve = function (obj) {
this.state = 'fulfilled'
var promise = this.promise
var handler = {}
while (handler = promise.handlerQueue.shift()) {
if (handler && handler.resolve) {
var res = handler.resolve(obj)
if (res && res.isPromise) {
res.handlerQueue = promise.handlerQueue
this.promise = res
return;
} else {
obj = res
}
}
}
}
Deferred.prototype.reject = function (obj) {
this.state = 'rejected'
var promise = this.promise
var handler = {}
while (handler = promise.handlerQueue.shift()) {
if (handler && handler.reject) {
var res = handler.reject(obj)
if (res && res.isPromise) {
res.handlerQueue = promise.handlerQueue
this.promise = res
return;
} else {
obj = res
}
}
}
}
//------ test-------//
function asyncDosomeing(flag, name) {
const deferred = new Deferred()
setTimeout(function () {
if (flag) {
deferred.resolve({code: 200, message: '成功', name: name})
} else {
deferred.reject({code: 400, message: '失败', name: name})
}
}, 2000)
return deferred.promise
}
asyncDosomeing(true, 'asyncDosomeing1').then(result => {
console.info(result)
return asyncDosomeing(false, 'asyncDosomeing2')
}).then(result => {
console.info(result)
return 'dadds'
}).then(result => {
console.info(result)
})
五、统一的异常处理(拒绝处理)
那现在,我们有个需求,想实现所有的拒绝统一在一个地方处理。而不是每个then方法都传一个rejected 回调,只希望then()方法可以,安安心心的处理成功的回调。
step1().then(step2).then(step3).catch(function(err){
// do something when err
})
加一个catch err 的回调,当出现异常就直接到这个流程上处理。
那我们就在promise 的原型上架一个catch方法,如下
Promise.prototype.catch = function (onRejected) {
var handler = {}
if (typeof onRejected === 'function') {
handler.reject = onRejected
}
this.handlerQueue.push(handler)
return this
}
//------ test-------//
function asyncDosomeing(flag, name) {
const deferred = new Deferred()
setTimeout(function () {
if (flag) {
deferred.resolve({code: 200, message: '成功', name: name})
} else {
deferred.reject({code: 400, message: '失败', name: name})
}
}, 2000)
return deferred.promise
}
asyncDosomeing(true, 'asyncDosomeing1').then(result => {
console.info(result)
return asyncDosomeing(false, 'asyncDosomeing2')
}).then(result => {
console.info(result)
return 'dadds'
}).then(result => {
console.info(result)
}).catch(err => {
console.info('catch')
console.info(err)
return asyncDosomeing(true, 'asyncDosomeing3----catch')
})
这样就可以实现,只要异步操作流程中有一步被拒绝,下面流程就自然中断,直接到catch回调中处理异常。
六、API Promise化
在编码的时候,想要用promise进行异步操作流程控制,就要将当前的异步回调函数封装成promise。在自己开发的时候,往往会去引用第三方的模块,然后发现这些模块的异步回调API 不支持promise写法。难道我们自己全部封装实现一遍?!这明显是不合理的。那我们就可以实现一个 方法可以批量将方法Promise化,相关代码如下:
1.在deferred原型上实现一个异步回调函数,回调执行后触发deferred resolve 和 reject的方法
Deferred.prototype.callBack = function () {
var that = this
return function (err, result) {
if (err) {
that.reject(err)
} else {
that.resolve(result)
}
}
}
2.定义一个Api Promise化 方法
/**
* 将异步操作转换成promise
*/
var promisify = function (method) {
if (typeof method !== 'function') {
throw new TypeError('is not a function')
}
return function () {
const defrred = new Deferred()
var args = Array.prototype.slice.call(arguments, 0) // 克隆参数
args.push(defrred.callBack())
method.apply(this, args)
return defrred.promise
}
}
最后我们就可以简化代码
var readFile = promisify(fs.readFile);
readFile('file.text').then(function(file){
return readFile(file.trim())
}).then(function(file2){
console.log(file2)
})
这里只是对promise/deferred 原理的简单实现,还有很多情况没有考虑。希望大家在做promise异步流程操作的时候,还是选择现在成熟的模块。比如 q模块、bulebird、when、或者 es6 的promise 去做。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。