16

一、什么是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 去做。


lyf87
73 声望3 粉丝