资讯专栏INFORMATION COLUMN

promise/deferred 模式原理分析和实现

gclove / 3427人阅读

摘要:三模式模式其实包含两部分和。六化在编码的时候,想要用进行异步操作流程控制,就要将当前的异步回调函数封装成。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87136.html

相关文章

  • Node_深入浅出Node

    摘要:简介项目命名为就是一个服务器单纯开发一个服务器的想法,变成构建网络应用的一个基本框架发展为一个强制不共享任何资源的单线程,单进程系统。单线程弱点无法利用多核错误会引起整个应用退出,应用的健壮性大量计算占用导致无法继续调用异步。 NodeJs简介 Ryan Dahl项目命名为:web.js 就是一个Web服务器.单纯开发一个Web服务器的想法,变成构建网络应用的一个基本框架.Node发展...

    shinezejian 评论0 收藏0
  • 读Zepto源码之Deferred模块

    摘要:为的项,取出来的分别为和,所以上的和方法,调用的是中的方法,实质是往各自的回调列表中添加回调函数。进度回调函数数组。参数为异步对象的索引值,参数为对应的上下文数组,即或,为对应的回调函数数组,即或。 Deferred 模块也不是必备的模块,但是 ajax 模块中,要用到 promise 风格,必需引入 Deferred 模块。Deferred 也用到了上一篇文章《读Zepto源码之C...

    yagami 评论0 收藏0
  • promise学习(2)

    摘要:所谓的能对状态进行操作的特权方法,指的就是能对对象的状态进行等调用的方法,而通常的的话只能在通过构造函数传递的方法之内对对象的状态进行操作。一般会在构造函数中编写逻辑,什么时候执行回调,什么时候执行回调。 原文地址 1. 在then中使用reject 如果一个promise最初只定义了resolve,但是还想要使用reject怎么办? 可以在then中返回一个新的promise。这个...

    firim 评论0 收藏0
  • Node.js 异步异闻录

    摘要:的异步完成整个异步环节的有事件循环观察者请求对象以及线程池。执行回调组装好请求对象送入线程池等待执行,实际上是完成了异步的第一部分,回调通知是第二部分。异步编程是首个将异步大规模带到应用层面的平台。 showImg(https://segmentfault.com/img/remote/1460000011303472); 本文首发在个人博客:http://muyunyun.cn/po...

    zzbo 评论0 收藏0
  • 再谈Promise

    摘要:方法完成回调注册模式下,对象通过方法调用,注册完成态和失败态的回调函数。这些回调函数组成一个回调队列,处理的值。调用实例的方法,能使注册的回调队列中的回调函数依次执行。 之前写了一篇关于ES6原生Promise的文章。近期又读朴灵的《深入浅出Node》,里面介绍了一个Promise/Deferred模式。 Promise是解决异步问题的利器。它其实是一种模式。Promise有三种状态,...

    chenjiang3 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<