一、前言                            

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

JS魔法堂:mmDeferred源码剖析相关推荐

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

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

  2. JS魔法堂:不完全国际化本地化手册 之 拓展篇

    前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...

  3. JS魔法堂:初探传说中的setImmediate函数

    一.前言   由于JavaScript程序为单线程,因此在执行长时间的操作时(如循环和递归操作)到导致UI线程长期被阻塞,无法响应用户操作请求(如点击按钮等),让用户体验大打折扣.于是想到将一个长时间 ...

  4. ThreadLocal源码剖析

    目录 一.ThreadLocal 1.1源码注释 1.2 源码剖析 散列算法-魔数0x61c88647 set操作 get操作 remove操作 1.3 功能测试 1.4 应用场景 二.变量可继承的T ...

  5. sarscape 将dem文件转化成stl_STL源码剖析 阅读笔记(一)介绍

    一.学习动机 对C++的理解:最近因为工作原因需要重新对C++进行学习,而上一次系统.全局的学习C++已经是在本科时期了,然后是读研期间的第一年学过一点皮毛,后来对C++的学习都是边用边学.纵然这样已 ...

  6. webgl编程指南源码_ThreeJS 源码剖析之 Renderer(一)

    引子? 最近,忽然想起曾在 WebGL 基础系列 文章中立下 flag:"后续还打算出 <ThreeJS 源码剖析> 系列"(特意翻出原话?),项目忙了一阵后,便决定开 ...

  7. data access components 2.0未响应_Vue2.x 源码剖析之响应式原理

    # Study Notes 本博主会持续更新各种前端的技术,如果各位道友喜欢,可以关注.收藏.点赞下本博主的文章. Vue.js 源码剖析-响应式原理 响应式处理的入口 src/core/insta ...

  8. React Native 启动流程 源码剖析

    开始之前   开始分析之前,新建一个名为 RnDemo 的空项目,RN 版本选择 0.58.1,查看项目自动为我们生成 MainActivity.java 和 MainApplication.java ...

  9. 【C++标准库】std::string用法指南源码剖析

    文章目录 1.ASCII码 (1)计算机如何表达字符 2.C 语言中的字符类型 char (1)思想:char 即整数 (3)C 语言帮手函数 (4)C语言中的字符串 (4)C 语言转义符 3.C++ ...

最新文章

  1. A Survey on Techniques in NLP--阅读笔记
  2. MDT2008部署之三LTI部署之二
  3. 为什么不走INDEX FAST FULL SCAN呢
  4. 关于Android studio 3.0 Failure [INSTALL_FAILED_TEST_ONLY]安装失败的问题
  5. 如何分辨PoE工业交换机是否标准供电
  6. ASP 代码当前记录集不支持更新问题的解决办法。
  7. HTTP 视频怎么在 MIP 页面中使用?
  8. 固件的完整形式是什么?
  9. [转]在Windows中安装PhpUnit
  10. CSS浏览器兼容性的4个解决方案:浏览器CSS样式初始化、浏览器私有属性,CSS hack语法和自动化插件...
  11. poj 2406 Power Strings kmp基础
  12. linux怎么卸载fishshell,为Bash/Zsh/Fish安装Starship Shell提示符的步骤
  13. 如何把多个PDF页面合并成一页PDF - PDF页面合并器使用方法
  14. 搞不动了,持安零信任真的安全
  15. CloudSim仿真流程研究(一)
  16. matlab一维数组操作,Matlab创建一维数组的具体操作讲解
  17. Office系列函数之Left函数的使用
  18. vue:无法加载文件..
  19. linux 设置深信服easyconnect 代理
  20. 在linux下 wget 下载报错 http request sent,awaiting response... 404 not found

热门文章

  1. html5手机端设置date,如何在移动端更好地使用HTML5 date input
  2. c语言le后能跟变量吗,【C语言】C语言常量和变量
  3. 西北师范计算机考研调剂,西北师范大学研究生调剂
  4. oss生成唯一文件名_根据结构化自然语言规范自动生成精确预言
  5. 基于熵权法优劣解距离法_维普资讯中文期刊服务平台-基于改进TOPSIS方法的航空装备预研项目技术风险评估...
  6. css中的margin和padding
  7. 工业机器视觉系统相机如何选型?(理论篇—3)
  8. 力扣(LeetCode)刷题,简单+中等题(第32期)
  9. Linux那些事儿之我是Sysfs(11)sysfs 创建普通文件
  10. OpenCV畸变校正原理以及损失有效像素原理分析