这篇文章可以看作是屈屈同学关于when.js的文章《异步编程:When.js快速上手》的续篇。

屈屈的文章中详细介绍了when.js,在这里关于when.js的使用我就不多复述了,大家可以自己去研究它的API。

在这里,我主要想讨论的是如何实现一个when.js类似的promise/A框架。为了更清晰了解实现原理,我略过when.js中一些比较强大的功能,只实现其中最核心的功能,包括基本的then(),otherwise()以及比较好用的all()和any()。

下面看一下Promise的基本数据结构:

  1. function Promise(){
  2. this._resolves = []; //resolve回调数组
  3. this._rejects = []; // reject回调数组
  4. this._readyState = Promise.PENDING; //状态标识
  5. this._data = null; //resolve回调的实参
  6. this._reason = null; //reject回调的实参
  7. }
  8. //在构造函数上 扩展属性
  9. mix(Promise, {
  10. PENDING : 0,
  11. FULFILLED : 1,
  12. REJECTED : 2,
  13. isPromise: function(obj){// 检测是否promise对象
  14. return obj != null && typeof obj['then'] == 'function';
  15. }
  16. });

我们可以看到,一个Promise包含五个属性

  1. _resolves数组用来存放当状态转换为FULFILLED之时需要执行的动作,
  2. _rejects数组用来存放当状态转换为REJECTED时需要执行的动作,
  3. _readyState属性用来存放当前的Promise对象的状态,
  4. _data属性用来存放调用resolve时传递参数,
  5. _reason属性用来存放调用reject时传递的参数。

详细的参数说明我们继续看后面的实现会比较明白:

  1. // 扩展Promise的原型对象
  2. mix(Promise.prototype, {
  3. then: function(onFulfilled, onRejected){
  4. var deferred = new Defer(); //创建延迟对象
  5. function fulfill(data){ //对传入的resolve回调进行包装
  6. var ret = onFulfilled ? onFulfilled(data) : data;
  7. if(Promise.isPromise(ret)){//若原始resolve回调返回promise对象
  8. ret.then(function(data){
  9. deferred.resolve(data);//回调的promise被resolve后 才resolve自身的deferred
  10. });
  11. }else{//原始回调返回的是普通值 则直接resolve创建的延迟对象
  12. deferred.resolve(ret);
  13. }
  14. return ret;//真正resolve回调返回原始回调的结果
  15. }
  16. if(this._readyState === Promise.PENDING){//promise在等待兑现中...
  17. this._resolves.push(fulfill); //添加回调
  18. if(onRejected){
  19. this._rejects.push(onRejected);
  20. }else{//没有传入reject回调 则添加一个默认的
  21. //为了让reject向后传递
  22. this._rejects.push(function(reason){
  23. deferred.reject(reason);
  24. });
  25. }
  26. }else if(this._readyState === Promise.FULFILLED){//执行then时,promise已完成 则马上执行成功回调
  27. var self = this;
  28. setTimeout(function(){
  29. fulfill(self._data);
  30. });
  31. }
  32. return deferred.promise;// then 返回承诺对象
  33. },
  34. otherwise: function(onRejected){//语法糖 不入参resolve回调
  35. return this.then(undefined, onRejected);
  36. }
  37. });

Promise.prototype.then 是整个组件里面最复杂的地方,代码直接阅读可能看起来会比较不明白,我后面会详细讲,在这里先暂时把这个方法做一个简化,便于大家理解其中最核心的内容:

  1. mix(Promise.prototype, {
  2. then: function(onFulfilled, onRejected){
  3. if(this._readyState === Promise.PENDING){
  4. if(onFulfilled){
  5. this._resolves.push(onFulfilled);
  6. }
  7. if(onRejected){
  8. this._rejects.push(onRejected);
  9. }
  10. }else if(this._readyState === Promise.FULFILLED){
  11. return onFulfilled && onFulfilled(this._data);
  12. }
  13. },
  14. otherwise: function(onRejected){
  15. return this.onFulfilled(undefined, onRejected);
  16. }
  17. });

简化成这样,看起来就简单明了了吧,实际上就是当Promise状态为PENDING的时候,如果有执行then,需要将onFulfilled和onReject暂存起来,等到真正的异步操作执行完成后再触发。那么为什么这样简单的写法不行,需要上面那种复杂写法呢?我们慢慢来往下看——

resolve和reject两个方法就很简单了,实际上就是看是否有暂存起来的操作需要执行,如果有的话,就把这些操作执行了。

  1. function Defer(){
  2. this.promise = new Promise();//创建并保存promise对象
  3. }
  4. mix(Defer.prototype,{
  5. resolve: function(data){//延迟对象resolve时
  6. var promise = this.promise;
  7. if(promise._readyState != Promise.PENDING){//若promise已完成 什么都不做
  8. return;
  9. }
  10. promise._readyState = Promise.FULFILLED; //状态标识为完成
  11. promise._data = data;
  12. ArrayH.forEach(promise._resolves, function(handler){//执行回调
  13. handler(data);
  14. });
  15. },
  16. reject: function(reason){//当延迟对象reject时 逻辑同上
  17. var promise = this.promise;
  18. if(promise._readyState != Promise.PENDING){
  19. return;
  20. }
  21. promise._readyState = Promise.REJECTED;
  22. promise._reason = reason;
  23. var handler = promise._rejects[0];
  24. if(handler){
  25. handler(reason);
  26. }
  27. }
  28. });

这里我用了和when.js一样的思路,将resolve和reject定义在一个新的Defer对象上,这样是为了将这两个方法封装在使用promise的方法内部,避免使用者让promise在外部操作状态改变,从而增加程序复杂度。

有了这个Defer之后,我们就可以很方便地将一个方法写成Promise了——

function Test(){

var deferred = new Defer();

QW.getJSONP(api, function(data){

deferred.resolve(data[0]);

});

return deferred.promise;

}

写法上是不是跟when.js一样?

但是简化版的Promise有个很重要的问题没有解决——then的链式调用。因为如果没有链式调用,就没法解决异步嵌套的问题,那样promise也就失去了存在的意义。

现在我们再回过头来看看为什么要写复杂的then——

  1. mix(Promise.prototype, {
  2. then: function(onFulfilled, onRejected){
  3. var deferred = new Defer();
  4. function fulfill(data){
  5. var ret = onFulfilled ? onFulfilled(data) : data;
  6. if(Promise.isPromise(ret)){
  7. ret.then(function(data){
  8. deferred.resolve(data);
  9. });
  10. }else{
  11. deferred.resolve(ret);
  12. }
  13. return ret;
  14. }
  15. if(this._readyState === Promise.PENDING){
  16. this._resolves.push(fulfill);
  17. if(onRejected){
  18. this._rejects.push(onRejected);
  19. }else{
  20. //为了让reject向后传递
  21. this._rejects.push(function(reason){
  22. deferred.reject(reason);
  23. });
  24. }
  25. }else if(this._readyState === Promise.FULFILLED){
  26. var self = this;
  27. setTimeout(function(){
  28. fulfill(self._data);
  29. });
  30. }
  31. return deferred.promise;
  32. },
  33. otherwise: function(onRejected){
  34. return this.then(undefined, onRejected);
  35. }
  36. });

我们看一下类似于下面这种调用情况——

  1. var getData = function() {
  2. var deferred = when.defer();
  3. $.getJSON(api, function(data){
  4. deferred.resolve(data[0]);
  5. });
  6. return deferred.promise;
  7. }
  8. var getImg = function(src) {
  9. var deferred = when.defer();
  10. var img = new Image();
  11. img.onload = function() {
  12. deferred.resolve(img);
  13. };
  14. img.src = src;
  15. return deferred.promise;
  16. }
  17. var showImg = function(img) {
  18. $(img).appendTo($('#container'));
  19. }
  20. getData()
  21. .then(getImg)
  22. .then(showImg);

这段代码在屈屈童鞋的那篇文章中出现,它最重要的是 getData().then(getImg).then(showImg) 这种链式形式,表示先通过jsonp获得image数据,然后再通过数据展现出图片,这种化异步嵌套为可读性更好的链式调用形式正是promise规范存在的意义所在,那么如何实现这一点呢?

仔细观察可以发现,如果把前面两级看作一个整体,(getData().then(getImg)).then(showImg)显然是一个单一的promise,这个promise我们可以通过一个范式来表达一下——

  1. A().then(B).then(C) =>
  2. A().then(B) ==
  3. (function(){
  4. var deferred = new Defer();
  5. A().then(function(){
  6. var ret = B.apply(this, arguments);
  7. if(isPromise(ret)){
  8. ret.then(function(data){
  9. deferred.resolve(data);
  10. });
  11. }else{
  12. deferred.resolve(ret);
  13. }
  14. return ret;
  15. });
  16. return deferred.promise;
  17. })();

上面这个代码是什么意思呢?其实就是说,要实现A().then(B).then(C),其实等价于需要 A().then(B)返回一个新的Promise,而这个新的Promise是相当于当then(B)中的B被调用的时候,执行resolve操作,所以用以下方法传给A的resolve队列替代原先的”B”方法即可——

  1. function(){
  2. var ret = B.apply(this, arguments);
  3. if(isPromise(ret)){
  4. ret.then(function(data){
  5. deferred.resolve(data);
  6. });
  7. }else{
  8. deferred.resolve(ret);
  9. }
  10. return ret;
  11. }

想通了上面这一点,就好理解那个复杂的then了,正是做了这件事情,用下面的方法——

  1. function(){
  2. var ret = onFulfilled.apply(this, arguments);
  3. if(isPromise(ret)){
  4. ret.then(function(data){
  5. deferred.resolve(data);
  6. });
  7. }else{
  8. deferred.resolve(ret);
  9. }
  10. return ret;
  11. }

替代了直接push进onFulfilled到_resolves。

讲到这里,我想强调一下,promise规范的神奇之处就在这里了——我们恰恰是用了promise规范本身实现了这个规范实现的最难之处——then的链式调用~

写通了这个核心部分,那么剩下的功能就不复杂了,我们既然可以用promise规范来实现promise本身的核心代码,当然也可以用它来实现all和any等功能了,那些相对来说都会是非常简单的问题——

  1. QW.P = {
  2. defer: function(){
  3. return new Defer();
  4. },
  5. all: function(promises){
  6. var deferred = QW.P.defer();
  7. var n = 0, result = [];
  8. ArrayH.forEach(promises, function(promise){
  9. promise.then(function(ret){
  10. result.push(ret);
  11. n++;
  12. if(n >= promises.length){
  13. deferred.resolve(result);
  14. }
  15. });
  16. });
  17. return deferred.promise;
  18. },
  19. any: function(promises){
  20. var deferred = QW.P.defer();
  21. ArrayH.forEach(promises, function(promise){
  22. promise.then(function(ret){
  23. deferred.resolve(ret);
  24. });
  25. });
  26. return deferred.promise;
  27. }
  28. };
  29. QW.defer = QW.P.defer;

从上面的代码可以看到,all和any都可以通过promise本身轻松实现,其逻辑并不复杂。顺便我们实现了QW.P.defer()这个语法糖。上面的代码的例子是基于QWrap的,但是我们会发现将它独立出来并不复杂,因为它只是依赖于ArrayH.forEach和ObjectH.mix,直接从QW中copy过来这两个方法就好了。

最后我们看一下完整的代码——

  1. (function(){
  2. var mix = QW.ObjectH.mix,
  3. ArrayH = QW.ArrayH;
  4. function Promise(){
  5. this._resolves = [];
  6. this._rejects = [];
  7. this._readyState = Promise.PENDING;
  8. this._data = null;
  9. this._reason = null;
  10. }
  11. mix(Promise.prototype, {
  12. then: function(onFulfilled, onRejected){
  13. var deferred = new Defer();
  14. function fulfill(data){
  15. var ret = onFulfilled ? onFulfilled(data) : data;
  16. if(Promise.isPromise(ret)){
  17. ret.then(function(data){
  18. deferred.resolve(data);
  19. });
  20. }else{
  21. deferred.resolve(ret);
  22. }
  23. return ret;
  24. }
  25. if(this._readyState === Promise.PENDING){
  26. this._resolves.push(fulfill);
  27. if(onRejected){
  28. this._rejects.push(onRejected);
  29. }else{
  30. //为了让reject向后传递
  31. this._rejects.push(function(reason){
  32. deferred.reject(reason);
  33. });
  34. }
  35. }else if(this._readyState === Promise.FULFILLED){
  36. var self = this;
  37. setTimeout(function(){
  38. fulfill(self._data);
  39. });
  40. }
  41. return deferred.promise;
  42. },
  43. otherwise: function(onRejected){
  44. return this.then(undefined, onRejected);
  45. }
  46. });
  47. mix(Promise, {
  48. PENDING : 0,
  49. FULFILLED : 1,
  50. REJECTED : 2,
  51. isPromise: function(obj){
  52. return obj != null && typeof obj['then'] == 'function';
  53. }
  54. });
  55. function Defer(){
  56. this.promise = new Promise();
  57. }
  58. mix(Defer.prototype,{
  59. resolve: function(data){
  60. var promise = this.promise;
  61. if(promise._readyState != Promise.PENDING){
  62. return;
  63. }
  64. promise._readyState = Promise.FULFILLED;
  65. promise._data = data;
  66. ArrayH.forEach(promise._resolves, function(handler){
  67. handler(data);
  68. });
  69. },
  70. reject: function(reason){
  71. var promise = this.promise;
  72. if(promise._readyState != Promise.PENDING){
  73. return;
  74. }
  75. promise._readyState = Promise.REJECTED;
  76. promise._reason = reason;
  77. var handler = promise._rejects[0];
  78. if(handler){
  79. handler(reason);
  80. }
  81. }
  82. });
  83. QW.P = {
  84. defer: function(){
  85. return new Defer();
  86. },
  87. isPromise: function(promiseOrValue){
  88. return Promise.isPromise(promiseOrValue);
  89. },
  90. all: function(promises){
  91. var deferred = QW.P.defer();
  92. var n = 0, result = [];
  93. ArrayH.forEach(promises, function(promise){
  94. promise.then(function(ret){
  95. result.push(ret);
  96. n++;
  97. if(n >= promises.length){
  98. deferred.resolve(result);
  99. }
  100. });
  101. });
  102. return deferred.promise;
  103. },
  104. any: function(){
  105. var deferred = QW.P.defer();
  106. ArrayH.forEach(promises, function(promise){
  107. promise.then(function(ret){
  108. deferred.resolve(ret);
  109. });
  110. });
  111. return deferred.promise;
  112. }
  113. };
  114. QW.defer = QW.P.defer;
  115. })();

转: when.js原理和核心实现的更多相关文章

  1. jquery-2 jQuery原理和核心方法(多看学习视频)

    jquery-2  jQuery原理和核心方法(多看学习视频) 一.总结 一句话总结:jQuery就是普通的js对象,只不过方法比较多而已,属性就length一个. 1.jquery的链式操作的底层原 ...

  2. 20道JS原理题助你面试一臂之力!(转)

    20道JS原理题助你面试一臂之力! 前言 本文针对目前常见的面试题,仅提供了相应的核心原理及思路,部分边界细节未处理.后续会持续更新,希望对你有所帮助. 1. 实现一个call函数 // 思路:将要改 ...

  3. mark jquery 链式调用的js原理

    我们在使用jquery的时候会用到类似$("#id").css('color','red').show(200); 这样写有点减少代码量,减少了逐步查询DOM的性能损耗: js 原 ...

  4. Hbase的架构原理、核心概念

    Hbase的架构原理.核心概念 1.Hbase的表.行.列.列族 2.核心组件: Table和region Table在行的方向上分割为多个HRegion, 一个region由[startkey,en ...

  5. 李洪强iOS开发之RunLoop的原理和核心机制

    李洪强iOS开发之RunLoop的原理和核心机制 搞iOS之后一直没有深入研究过RunLoop,非常的惭愧.刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研 ...

  6. js 类型系统的核心:元类型、原型链与内省机制

    js 类型系统的核心:元类型.原型链与内省机制 二.JS数据类型 下面就来看看JS中的数据类型,在js中定义了如下几种数据类型:大方向上分为 基本数据类型(简单数据类型) 和 引用数据类型(复杂数据类 ...

  7. Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件

    本章内容 自定义属性快速入门 外化配置 自动配置 自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 图 2 第二章目录结构图 第 2 章 Spr ...

  8. jQuery的实现原理和核心

    1.jQuery的实现原理 1)jQuery采用的是构造函数模式进行开发的,jQuery是一个类 2)上面说的常用的方法(CSS.属性.筛选.事件.动画.文档处理)都是定义在jQuery.protot ...

  9. Three.js 入门指南(核心对象)

    推荐大家可以看看这个:http://wenku.baidu.com/link?url=RQU2exzV_EF3GATc3bzQU2o9LGMuCmiN5nUJth5SLG3E2TrxtBLQodJU_ ...

随机推荐

  1. java中的堆、栈、常量池

    java中的堆.栈.常量池 分类: java2010-01-15 03:03 4248人阅读 评论(5) 收藏 举报 javastring编译器jvm存储equals Java内存分配: 1. 寄存器 ...

  2. 学习C++语言的50条忠告

    50条忠告:(其中有几条觉得写的不够贴切,所以删了,发了余下的部分) 1.把C++当成一门新的语言学习: 2.看<Thinking In C++>,不要看<C++变成死相>: ...

  3. MySQL函数笔记

    MySQL函数笔记 日期函数 SELECT t1.xcjyrq, t1.* FROM view_sbxx t1 WHERE t1.syzt ; SELECT t1.xcjyrq, t1.* FROM ...

  4. 走进C标准库(2)——"stdio.h"中的fopen函数

    其他的库文件看起来没有什么实现层面的知识可以探究的,所以,直接来看stdio.h. 1.茶余饭后的杂谈,有趣的历史 在过去的几十年中,独立于设备的输入输出模型得到了飞速的发展,标准C从这个改善的模型中 ...

  5. selenium webdriver 学习笔记(一)

    selenium webdriver 第一个脚本: #coding = utf-8 from selenium import webdriver import time url = "htt ...

  6. ubuntu rc.local 无效 解决方案(转)

    为了让mysql开机启动,我将mysql命令添加到/etc/rc.local中,但怎么也运行不了.一开始认为只是/etc/rc.local的权限问题,但通过以下命令修改后,还是不起作用. sudo c ...

  7. Office 2010 Toolkit and EZ-Activator

    “Office 2010 Toolkit 2.0.1”是“迷你KMS”的更新换代版本.虽然是单一可执行程序,但一身承担两大职能:“KMS服务器”和“客户激活端”.“Office 2010 Toolki ...

  8. iOS6和iOS7代码的适配(4)——tableView

    iOS7上不少控件的样子有了变化(毕竟要扁平化嘛),不过感觉变化最大的肯定非tableView莫属.因为这个控件的高度可定制性,原先是使用及其广泛的,这样的一个改变自然也影响颇大. 1.accesso ...

  9. kvm在线磁盘扩展

    1,查看指定kvm虚拟机的现有磁盘domblklist

  10. centos6.5vpn搭建

    centos6.5vpn搭建整个搭建流程,服务端,客户端安装及测试. 达到的效果: 在安装vpn客户端的机器可通过vpn(virtual private network)专用线路(vpn主配置文件中定 ...