一、前言                            

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')
})
}, )

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

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 = false
function 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(, onReject)
},
ensure: function(onEnsure) {
return _post(, , , 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 = arguments
if (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 = -, fns = arguments;
"resolve,reject,notify,ensure".replace(/\w+/g, function(method) {
var fn = fns[++index];
if (typeof fn === "function") {
dirty = true
if (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", value
if (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.ensure
if (noop !== ensure) {
try {
ensure.call(this)//模拟finally
} catch (e) {
next = "reject";
array = [e];
}
}
var nextDeferred = this.promise._next
if (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 = , result = [], end
function 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 = true
deferred.reject(e);
})
}
for (var i = , 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.nextTick
return Deferred
})

四、总结                            

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

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

五、参考                            

《JavaScript框架设计》

JS魔法堂:mmDeferred源码剖析的更多相关文章

  1. JS魔法堂:jsDeferred源码剖析

    一.前言 最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(<JavaScript框架设计& ...

  2. JS魔法堂:剖析源码理解Promises/A规范

    一.前言 Promises/A是由CommonJS组织制定的异步模式编程规范,有不少库已根据该规范及后来经改进的Promises/A+规范提供了实现 如Q, Bluebird, when, rsvp. ...

  3. JS魔法堂:IMG元素加载行为详解

    一.前言 在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨. 二.资源加载的相关属 ...

  4. JS魔法堂:属性、特性,傻傻分不清楚

    一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...

  5. JS魔法堂:LINK元素深入详解

    一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...

  6. JS魔法堂:函数节流(throttle)与函数去抖(debounce)

    一.前言 以下场景往往由于事件频繁被触发,因而频繁执行DOM操作.资源加载等重行为,导致UI停顿甚至浏览器崩溃. 1. window对象的resize.scroll事件 2. 拖拽时的mousemov ...

  7. Node 进阶:express 默认日志组件 morgan 从入门使用到源码剖析

    本文摘录自个人总结<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 章节概览 morgan是express默认的日志中间件, ...

  8. socket_server源码剖析、python作用域、IO多路复用

    本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...

  9. JS魔法堂:追忆那些原始的选择器

    一.前言                                                                                                 ...

随机推荐

  1. C#中virtual与abstract的区别

    C#中virtual与abstract的区别 C#的virtual & abstract经常让人混淆,这两个限定词都是为了让子类进行重新定义,覆盖父类的定义.但是用法上差别很大. a)     ...

  2. [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现

    一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...

  3. 【Java】ThreadLocal细节分析

    ThreadLocal通过中文解释就是线程本地变量,是线程的一个局部变量.根据哲学家黑格尔“的存在即合理”的说法,ThreadLocal的出现肯定是有它的意义,它的出现也是因为多线程的一个产物.Thr ...

  4. 作业三 代码规范 代码复审 PSP

    1.是否需要有代码规范(5分) 对于是否需要有代码规范,请考虑下列论点并反驳/支持: 1这些规范都是官僚制度下产生的浪费大家的编程时间.影响人们开发效率, 浪费时间的东西. 反对.我并不认为代码规范都 ...

  5. 用Nim语言开发windows GUI图形界面程序

    前言 本文得到了“樂師”的大力支持, 我们一起调试程序到深夜,要是没有他的帮忙, 我不知道要多久才能迈过这道坎, 另外“归心”还有其他人也提供了帮助, 他们都来自于QQ群:“Nim开发集中营”4693 ...

  6. YprogressBar,html5进度条样式,js进度条插件

    简介 YprogressBar是一款基于HTML5的进度条插件. YprogressBar是一款轻量级进度条插件,使用方便,资源占用少,模仿好压的解压界面,带有数字显示,同时支持在描述中增加参数,以动 ...

  7. Linux time命令

    说明:喜欢写小程序的人都特别注重自己程序的执行效率,那么在Linux上,就有一个time的命令,用于测量命令的运行时间,还可以测量内存.I/O等的使用情况. 一个程序在运行时使用的系统资源通常包括CP ...

  8. java数学函数库 API(转)

    原文地址:http://www.24xuexi.com/w/2011-11-08/98206.html 首先给大家看看Math类所提供的主要方法,下面的列表给出了Math类的主要方法,如果要理解Mat ...

  9. 02- Shell脚本学习--运算符

    Shell运算符 Bash 支持很多运算符,包括算数运算符.关系运算符.布尔运算符.字符串运算符和文件测试运算符. 算术运算符 原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 aw ...

  10. Jsp练习——连接数据库模拟登录

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...