首页 > JS魔法堂:mmDeferred源码剖析

JS魔法堂:mmDeferred源码剖析

一、前言                            

  avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢。项目请见:mmDeferred@github

 

二、API说明                          

   {Deferred} Deferred({Function|Object} mixin?) ,创建一个Deferred实例,当mixin的类型为Object时,将mixin的属性和函数附加到Deferred实例的Promise实例上。

    {String} state() ,获取当前Deferred实例的状态,含三种状态:pending,fulfilled,rejected;转换关系为:pending->fulfilled,pending-rejected。

   {Promise} then({Function} resolvefn?, {Function} rejectfn?, {Function} notifyfn?, {Function} ensurefn?) ,向当前的Deferred实例添加四类回调函数,并返回一个新的Promise实例。其中resolvefn是实例状态转换为fulfilled时调用,而rejectfn是实例状态转换为rejected时调用,而notifyfn则相当于Promises/A+规范中的progressHandler一样与实例状态无关只需调用notify函数则调用notifyfn,ensurefn的作用为模拟当前Deferred实例执行resolvefn、rejectfn和notifyfn的finally语句块,无论执行前面三个函数的哪一个均会执行ensurefn。

    {Promise} otherwise({Function} rejectfn?) ,向当前的Deferred实例添加rejectfn回调函数,并返回一个新的Promise实例。

    {Promise} ensure({Function} ensurefn?) ,向当前的Deferred实例添加ensurefn回调函数,并返回一个新的Promise实例。

    {undefined} resolve(...[*]) ,用于触发fulfill回调——也就是触发调用当前Deferred实例的resolvefn函数的请求,仅能调用一次。

    {undefined} reject(...[*]) ,用于触发reject回调——也就是触发调用当前Deferred实例的rejectfn函数的请求,仅能调用一次。

    {undefined} notify(...[*]) ,用于触发notify回调——也就是触发调用当前Deferred实例的notifyfn函数的请求,能调用多次。

   {Promise} Deferred.all(...[Promise]) ,要求传入多个Promise对象,当它们都正常触发时,就执行它的resolve回调。相当于jQuery的when方法,但all更标准,是社区公认的函数。

     {Promise} Deferred.any(...[Promise]) ,要求传入多个Promise对象,最先正常触发的Promise对象,将执行它的resolve回调。

 

、源码剖析                              

  首先要了解的是mmDeferred中存在Deferred和Promise两个操作集合(两者操作同一个的数据结构实例),Promise用于向实例添加四类回调函数,而Deferred用于发起实例状态变化或触发回调函数调用的操作,并且限制为仅通过Deferred函数返回的为Deferred操作集合,而其他API返回的均为Promise操作集合。

  另外,值得注意的有以下几点

  1. mmDeferred在实例状态转换的实现方式上是采取先调用回调函数再修改实例状态的方式;

  2. resolve、reject等的实现上并不是统一采用异步调用的方式在执行回调函数,而是当实例已经被添加了回调函数时同步执行回调函数,当未添加回调函数时则发起异步调用,让当前执行的代码块有机会向实例添加回调函数;

  3. 不支持以下方式的回调函数晚绑定:

var deferred = Deferred()
deferred.resolve()
setTimeout(function(){deferred.promise.then(function(){console.log('hello world')})
}, 0)

  在代码结构上值得注意的是

  1. 利用JS中变量声明自动提升(hoist)的特性,通过前置return语句将对外接口与具体实现的代码分离。

  2. 提取resolve、reject等函数的共性到私有函数_fire中,提供then、otherwise等函数的共性到私有函数_post中,提供Deferred.all和Deferred.any的共性到私有函数some中,避免重复代码从而大大减少代码量。

  待改进点我觉得应该将_fire和_post函数移出至Deferred函数之外,通过入参取代闭包引用外部变量的方式来获取和修改实例属性,那么每次调用Deferred函数时就不会重新声明新的_fire和_post函数了。

  存在疑惑的地方为

    假设当前实例A状态为pending,那么执行notify回调函数后当前实例A的状态是不变的,当后续执行的ensure函数抛出异常,那么将调用链表中下一个实例B的reject方法导致实例B的状态为rejected,但实例A状态依然为pending。这时再次调用实例B的resolve或reject方法均不会触发执行相应的回调函数,但可通过调用实例A的resovle或reject方法执行实例A和实例B相应的回调函数。

  下面是源码

define("mmDeferred", ["avalon"], function(avalon) {var noop = function() {}function Deferred(mixin) {var state = "pending"// 标识是否已经添加了回调函数, dirty = falsefunction ok(x) {state = "fulfilled"return x}function ng(e) {state = "rejected"// 将异常往后传递throw e}// Deferred实例var dfd = {callback: {resolve: ok,reject: ng,notify: noop,ensure: noop},dirty: function() {return dirty},state: function() {return state},promise: {then: function() {return _post.apply(null, arguments)},otherwise: function(onReject) {return _post(0, onReject)},ensure: function(onEnsure) {return _post(0, 0, 0, onEnsure)},_next: null}}if (typeof mixin === "function") {mixin(dfd.promise)} else if (mixin && typeof mixin === "object") {for (var i in mixin) {if (!dfd.promise[i]) {dfd.promise[i] = mixin[i]}}}"resolve,reject,notify".replace(/w+/g, function(method) {dfd[method] = function() {var that = this, args = argumentsif (that.dirty()) {// 若已经添加了回调函数,则马上同步调用
                    _fire.call(that, method, args)} else {// 若未添加回调函数,则发起异步调用,让当前代码块的后续部分有足够的时间添加回调函数
                    Deferred.nextTick(function() {_fire.call(that, method, args)})}}})return dfd/** 精彩之处:* 由于JS会将变量声明自动提升(hoist)到代码块的头部* 因此这里将私有方法写在return语句之后从而更好地格式化代码结构*/// 添加回调函数到当前Deferred实例上
        function _post() {var index = -1, fns = arguments;"resolve,reject,notify,ensure".replace(/w+/g, function(method) {var fn = fns[++index];if (typeof fn === "function") {dirty = trueif (method === "resolve" || method === "reject") {// 将修改Deferred实例状态的功能封装到回调函数中// 也就是先调用回到函数再修改实例状态dfd.callback[method] = function() {try {var value = fn.apply(this, arguments)state = "fulfilled"return value} catch (err) {state = "rejected"return err}}} else {dfd.callback[method] = fn;}}})// 创建链表的下一个Deferred实例var deferred = dfd.promise._next = Deferred(mixin)return deferred.promise;}function _fire(method, array) {var next = "resolve", valueif (this.state() === "pending" || method === "notify") {var fn = this.callback[method]try {value = fn.apply(this, array);} catch (e) { //处理notify的异常value = e}if (this.state() === "rejected") {next = "reject"} else if (method === "notify") {next = "notify"}array = [value]}var ensure = this.callback.ensureif (noop !== ensure) {try {ensure.call(this)//模拟finally} catch (e) {next = "reject";array = [e];}}var nextDeferred = this.promise._nextif (Deferred.isPromise(value)) {// 如果回调函数返回值为Deferred实例,那么就将该实例插入nextDeferred之前value._next = nextDeferred} else {if (nextDeferred) {_fire.call(nextDeferred, next, array);}}}}window.Deferred = Deferred;Deferred.isPromise = function(obj) {return !!(obj && typeof obj.then === "function");};function some(any, promises) {var deferred = Deferred(), n = 0, result = [], endfunction loop(promise, index) {promise.then(function(ret) {if (!end) {result[index] = ret//保证回调的顺序n++;if (any || n >= promises.length) {deferred.resolve(any ? ret : result);end = true}}}, function(e) {end = truedeferred.reject(e);})}for (var i = 0, l = promises.length; i < l; i++) {loop(promises[i], i)}return deferred.promise;}Deferred.all = function() {return some(false, arguments)}Deferred.any = function() {return some(true, arguments)}Deferred.nextTick = avalon.nextTickreturn Deferred
})

 

四、总结                            

  源码中还提供了相关资料的链接,可以让我们更了解Promise/A+规范哦!

  尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4162646.html 肥子John

 

五、参考                            

《JavaScript框架设计》

转载于:https://www.cnblogs.com/fsjohnhuang/p/4162646.html

更多相关:

  • 本文概要: 1、首先给出结论是:除了几种特殊情况外,在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候通过属性来做。 2、讲解了使用getter、setter的好处。 3、列举了几种上面提到的特殊情况:有时不能使用setter、有时必须使用setter、有时必须使用getter。 注意:在对象之外访问实例变量时总是应该通过...

  •     在网上看到的这道题,答案是3次。因此a和b[2]实例化三次对象,就调用了三次构造方法,而*p[2]只定义了指针,并未实例化对象,所以不调用构造方法。  转载于:https://blog.51cto.com/sydugu/1553403...

  • 草色新雨中, 松声晚窗里。之前我们学习 Power Query 都是用鼠标就完成了很多复杂的操作。虽然 PowerQuery 已经将大部分常用功能内置成到功能区。基本能完成我们大部分的报表自动化功能。但是总有些复杂的或者个性化的问题是开发团队没有预先想到的,这时我们就需要学习 M 语言。一、M 语言在哪里?M语言的函数公式有三个地...

  • 前言从2020年3月份开始,计划写一系列文档--《小白从零开始学编程》,记录自己从0开始学习的一些东西。第一个系列:python,计划从安装、环境搭建、基本语法、到利用Django和Flask两个当前最热的web框架完成一个小的项目第二个系列:可能会选择Go语言,也可能会选择Vue.js。具体情况待定,拭目以待吧。。。基本概念表达式表...

  • 1.1函数1.1.1什么是函数函数就是程序实现模块化的基本单元,一般实现某一功能的集合。函数名:就相当于是程序代码集合的名称参数:就是函数运算时需要参与运算的值被称作为参数函数体:程序的某个功能,进行一系列的逻辑运算return 返回值:函数的返回值能表示函数的运行结果或运行状态。1.1.2函数的作用函数是组织好的,可重复使用的,用来...

  • 原标题:基于Python建立深度神经网络!你学会了嘛?图1 神经网络构造的例子(符号说明:上标[l]表示与第l层;上标(i)表示第i个例子;下标i表示矢量第i项)单层神经网络图2 单层神经网络示例神经元模型是先计算一个线性函数(z=Wx+b),接着再计算一个激活函数。一般来说,神经元模型的输出值是a=g(Wx+b),其中g是激活函数(...

  • 在学习MySQL的时候你会发现,它有非常多的函数,在学习的时候没有侧重。小编刚开始学习的时候也会有这个感觉。不过,经过一段时间的学习之后,小编发现尽管函数有很多,但是常用的却只有那几个。今天小编就把常用的函数汇总一下,为大家能够能好的学习MySQL中的函数。MySQL常使用的函数大概有四类。时间函数、数学函数、字符函数、控制函数。让我...

  • 阮一峰大神的关于jQuery的deferred对象详解   http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html 转载于:https://www.cnblogs.com/qiufang/p/888...