当前位置: 首页 > 编程日记 > 正文

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

相关文章:

asp vb 插入,更新,删除数据库操作。

记笔记。离开学校&#xff0c;东西都还给老师了&#xff0c;哎。Select Case str Case "insert": sql"select * from ["&tablename&"] where idnull" rs.open sql,conn,1,3 rs.addnew For Each key In request.Form …

第五次作业:四则运算之升级

本次作业要求来源&#xff1a;https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/2232 我的github地址&#xff1a;https://github.com/yellowjy/study 结对同伴的学号姓名&#xff1a;201606120069 缪国锋 一、基本要求&#xff1a; 生成题目&#xff0c;单个题目最多…

妙用vector:根据第一个不等的元素比较两个序列大小的利器

如下面的代码&#xff0c;可以看到向量容器va和vb的第六个元素是第一个不等的元素&#xff0c;且va[5]>vb[5]&#xff0c;因此输出va>vb时结果应该为1。 int main(){vector<int> va,vb;int a[10] {0,1,2,3,4,6};int b[10] {0,1,2,3,4,5,6,7,8,9};for(int i0;i&l…

四个超好用的优质资源搜索网站,海量优质资源等你发现!

在网上找资源的时候总找不到满意的优质资源&#xff1f;今天小编把办公室大佬珍藏多年的四个超好用优质资源搜索网站分享给你&#xff0c;只要你想找&#xff0c;没有找不到的资源&#xff01;一、学习资料库学习资料库中有大量的免费学习资料&#xff0c;学习资料涵盖多种学科…

sql server 2008 修改sa密码

问题&#xff1a; 当我们用windows本身验证之后需要修改sa密码&#xff0c;出现这样的错误。 解决方案&#xff1a; 转载于:https://www.cnblogs.com/hcfan/p/4164777.html

Sql Server数据库连接Oracle数据库

Select * From opendatasource(MSDAORA, Data SourceAPPDATA;User IDuname;Passwordpwd)..BYERP.MAT_CLASS APPDATA--连接字符串名称 用户.表名 http://www.cnblogs.com/luqingfei/articles/538005.html转载于:https://www.cnblogs.com/hanwater/archive/2009/12/04/1616864.ht…

普通二叉树、二叉查找树、平衡二叉树常见操作汇总

目录 总览表 普通二叉树 二叉查找树 平衡二叉树 总览表 普通二叉树 struct Node{int data;Node* lchild,rchild; };Node* newNode(int v){Node* node new Node;//申请变量的地址空间node->lchild node->rchild NULL;//新建的结点没有左右孩子node->data v;//…

Java IO系列之字节流拷贝文件性能比较

Java IO 字节流基类 InputStream--输入流&#xff0c; OutPutStream--输出流&#xff0c; 输入流用于读&#xff0c;输出流用于写. 字节流默认一次只读取或输出一个字节。 package jonavin.io;import java.io.BufferedInputStream; import java.io.BufferedOutputStream; impo…

引用 提高开发水平的几项必备技术

很好的一篇文章!!!&#xff08;偶遇此文&#xff0c;英雄所见略同&#xff01;&#xff09; 本文列出了当今计算机软件开发和应用领域最重要十种关键技术排名&#xff0c;如果你想保证你现在以及未来的几年不失业&#xff0c;那么你最好跟上这些技术的发展。虽然你不必对这十种…

移动磁盘由于IO设备错误,要怎样寻回文件

J盘打不开由于IO设备错误&#xff0c;是因为这个I盘的文件系统内部结构损坏导致的。要恢复里面的数据就必须要注意&#xff0c;这个盘不能格式化&#xff0c;否则数据会进一步损坏。具体的恢复方法看正文 工具/软件&#xff1a;流星数据恢复软件 步骤1&#xff1a;先百度搜索并…

1043 Is It a Binary Search Tree

1. 这是二叉查找树BST那节的习题&#xff0c;要做出来这题&#xff0c;需要具备的基础知识有&#xff1a;BST的创建&#xff0c;涉及函数createBST&#xff0c;insertBST&#xff0c;newNode&#xff0c;二叉树的先序遍历、后序遍历。 2. 需要转过来的弯的有&#xff1a; 给定…

运用比较纯的CSS打造很Web2.0的按钮

警告&#xff1a;如果你在使用IE浏览此文&#xff0c;那么请回避一下吧! 什么&#xff0c;你用的还是IE6&#xff1f;你真奥特曼(推荐你去打小怪兽)&#xff01; 先上图&#xff0c;所谓有图有真相。 如果您觉得图片上这些按钮不够2.0&#xff0c;那没办法&#xff0c;请回避吧…

Elasticsearch 参考指南(脚本)

脚本 脚本模块使你可以使用脚本来评估自定义表达式&#xff0c;例如&#xff0c;你可以使用脚本将“脚本字段”作为搜索请求的一部分返回&#xff0c;或者为查询评估自定义分数。 默认脚本语言是Painless&#xff0c;附加的lang插件使你可以运行用其他语言编写的脚本&#xff0…

eclipse如何卸载adt插件

1、选择 Help Install New Software&#xff1b; 2、在Details 面板中, 点击What is already installed? 链接&#xff1b; 3、在Eclipse Installation Details 对话框中&#xff0c;选择Android DDMS和Android Development Tools &#xff0c;然后点击Uninstall&#xff1b;…

1099 Build A Binary Search Tree

1. 本题给出了树的样子&#xff0c;给出了用来填充的数列&#xff0c;并且告诉是一棵二叉查找树。 2. 先用静态存储的方式将树的框架建立起。然后对数列进行小到大排序&#xff0c;利用BST中序遍历是升序的性质&#xff0c;通过中序遍历将数值填充的树中。 3. 层序输出的时候…

redis4.0.6集群部署(5.0.2版本更新补充)

Redis集群安装4版本需要ruby 5版本不需要ruby就能集群1集群机器分布192.168.1.133 redis1192.168.1.134 redis2192.168.1.135 redis32 免密登录ssh-keygenssh-copy-id 192.168.1.133ssh-copy-id 192.168.1.134ssh-copy-id 192.168.1.1353 关闭防火墙sy…

PHP多图片上传 并检查 加水印 源码

参数说明:$max_file_size : 上传文件大小限制, 单位BYTE$destination_folder : 上传文件路径$watermark : 是否附加水印(1为加水印,其他为不加水印);使用说明:1. 将PHP.INI文件里面的"extensionphp_gd2.dll"一行前面的;号去掉,因为我们要用到GD库;2. 将extension_dir…

C#中的委托和事件 (4)---事件和委托的编译代码

事件和委托的编译代码 这时候&#xff0c;我们不得不注释掉编译错误的行&#xff0c;然后重新进行编译&#xff0c;再借助Reflactor来对 event的声明语句做一探究&#xff0c;看看为什么会发生这样的错误&#xff1a; public event GreetingDelegate MakeGreet; 可以看到&#…

1066 Root of AVL Tree 需再做

1. 这题如果不知道平衡二叉树怎么平衡的(左旋右旋那一套)应该不可能做出吧&#xff0c;那就输出中位数回点血了。 2. 需要具备的基础知识&#xff1a;怎么将结点插入平衡二叉树。 3. 我犯的一个错误&#xff1a;把更新高度的函数直接返回了高度&#xff0c;而不没有修改高度。…

easyui-menu 解决disableItem不能禁用绑定事件的方法

版本&#xff1a;1.4. menu的disableItem方法不能禁用使用onClick方式绑定的事件。 解决思路如下&#xff1a; 重写disableItem方法和enableItem方法。 /*** menu方法扩展* param {Object} jq* param {Object} itemEl*/ $.extend($.fn.menu.methods, {/*** 激活选项&#xff0…

hadoop无法访问50070端口怎么办?

转载请注明出处&#xff1a;www.oldboyedu.com Hadoop 50070是hdfs的web管理页面&#xff0c;在搭建Hadoop集群环境时&#xff0c;有些大数据开发技术人员会遇到Hadoop 50070端口打不开的情况&#xff0c;引起该问题的原因很多&#xff0c;想要解决这个问题需要从以下方面进行排…

表情的机器自动识别(有图有真相)

这幅图片是我自己用C#编写的表情的机器自动识别。主要是AdaBoost的实现&#xff0c;训练做了几个不同版本&#xff1a;线性、并行和分布式&#xff0c;训练数据集采用的JAFFE。 有朋友问这东西有什么用处&#xff0c;其实主要是为了玩而已了。这是基于Paul Ekman那本著名的《情…

并查集专题练习:好朋友(未完待续)

有空再把题目补上 输入样例1 4 2 1 4 2 3 样例输出1 2 输入样例2 7 5 1 2 2 3 3 1 1 4 5 6 输出样例2 3 解题思路&#xff1a; 1. 这题放在并查集的专题后面&#xff0c;有查找也有合并&#xff0c;需要具备的基础只是是合并和查找的函数要会写(都很简单)。但是读到每…

android Viewpager取消预加载及Fragment方法的学习

1.在使用ViewPager嵌套Fragment的时候&#xff0c;由于VIewPager的几个Adapter的设置来说&#xff0c;都会有一定的预加载。通过设置setOffscreenPageLimit&#xff08;int number) 来设置预加载的熟练&#xff0c;在V4包中&#xff0c;默认的预加载是1&#xff0c;即使你设置为…

前端Js框架 UI框架汇总 特性 适用范围 选择

身为一个资深后端工程师&#xff0c;面对层出不穷的前端框架&#xff0c;总让人眼花缭乱&#xff0c;做一个综合解析贴&#xff0c;从全局着眼&#xff0c;让我们明白各种前端框架的应用范围&#xff0c;为如何选择前端框架&#xff0c;从不同的维度提供一些线索&#xff0c;做…

Emptyproject分析

Emptyproject分析(SimpleSample)1&#xff0c;InitApp()WinMain中有一个InitApp()&#xff0c;在sample中存在&#xff0c;但是在emptyproject中没有&#xff0c;该函数是用于设定已经声明的一些一般变量的初始值的。比如某些按钮。2&#xff0c;IsDeviceAcceptable()被WinMain…

1107 Social Clusters

这道题目给出的示例如上图所示&#xff0c;一共有1-8这8个人(圆形)&#xff0c;他们拥有1-10这10个兴趣爱好(方形)&#xff0c;一人可以有多个爱好&#xff0c;拥有共同爱好的人被视为一个社区的。现在给出每个人的爱好情况&#xff0c;求出社区的个数&#xff0c;并按照从大到…

c#直接调用ssis包实现Sql Server的数据导入功能

调用ssis包实现Sql Server的数据导入功能网上已经有很多人讨论过&#xff0c;自己参考后也动手实现了一下&#xff0c;上一次笔者的项目中还用了一下这个功能。思前想后&#xff0c;决定还是贴一下增强记忆&#xff0c;高手请54.1、直接调用ssis包&#xff0c;需要引用Microsof…

从入门到放弃的javaScrip——队列

队列数据结构 队列是遵循FIFO&#xff08;First In First Out&#xff0c;先进先出&#xff0c;也称为先来先服务&#xff09;原则的一组有序的项。队列在尾部添加新元素&#xff0c;并从顶部移除元素。最新添加的元素必须排在队列的末尾。 现实中&#xff0c;很常见的例子就是…

css控制不换行

white-space:nowrap; 转载于:https://www.cnblogs.com/w8104254/p/4178198.html