一、前言                            

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. 字符串反混淆实战 Dotfuscator 4.9 字符串加密技术应对策略

    因为手头需要使用一个第三方类库,网络上又找不到它的可用的版本,于是只好自己动手.这个类库使用了Dotfuscator 加密,用.NET Reflector加载程序集, 看到的字符串是乱码,如下面的代码 ...

  2. OWIN的理解和实践(三) –Middleware开发入门

    上篇我们谈了Host和Server的建立,但Host和Server无法产出任何有实际意义的内容,真正的内容来自于加载于Server的Middleware,本篇我们就着重介绍下Middleware的开发 ...

  3. 统计第一个空字符前面的字符长度(java实现)

    举例来说:char buf[] = {'a','b','c','d','e','f','\0','x','y','z'}当输入N=10或20,期待输出是6:当输入N=3或5,期待输出是3或5. pac ...

  4. rsync permission denied created directories have no permissions

    这个问题坑了好几天 最后被此贴解决 http://stackoverflow.com/questions/5798807/rsync-permission-denied-created-directo ...

  5. 把 Notepad++ 打造成一款易用的C#脚本编辑器

    以前一直用Linqpad在写小程序脚本,但是Linqpad自动完成功能要收费,且不开源,这样的话就不方便扩展了.今天在 http://csscriptnpp.codeplex.com/ 发现了一款C# ...

  6. Flyway, 数据库Schema管理利器

    整天跟数据库打交道的程序员都知道,当数据库的Schema发生改变时是多么痛苦的事情.尤其是一个在不断开发完善的项目,随着需求变化,数据库的schema也会跟着变化,而追踪记录这些变化一向都是费时费力. ...

  7. 如何在 IIS 上搭建 mercurial server

    mercurial server 对于代码管理工具,更多的人可能更熟悉 Git 一些(Git 太火了).其实另外一款分布式代码管理工具也被广泛的使用,它就是 mercurial.多人协作时,最好能够通 ...

  8. 我心中的核心组件(可插拔的AOP)~调度组件quartz.net续~任务管理器的开发

    回到目录 对于任务调度来说,越来越多的团队选择了quartz,它在java和.net环境下表现都十分优秀,配置简单,功能强大,时间表达式配置灵活,但在使用时,还是感觉缺点什么,怎么说,你在服务器上安装 ...

  9. Java程序员的日常 —— 响应式导航Demo

    这两天想要做响应式的页面,于是本着重复造轮子的想法,模仿Bootstrap官网,精简了一个响应式导航的Demo. 效果 代码 <!DOCTYPE html> <html> &l ...

  10. Atitit截屏功能的设计解决方案

    Atitit截屏功能的设计解决方案 自己实现.... 使用快捷键.. 弹出自己的win,,背景是屏幕快照 点击鼠标光标变成十字状态 出现截屏窗口调整截屏窗口位置与大小 释放鼠标,三个btn,,  复制 ...