异步编程系列教程:

  1. (翻译)异步编程之Promise(1)——初见魅力
  2. 异步编程之Promise(2):探究原理
  3. 异步编程之Promise(3):拓展进阶
  4. 异步编程之Generator(1)——领略魅力
  5. 异步编程之Generator(2)——剖析特性
  6. 异步编程之co——源码分析

动手实现Promise

在异步编程之Promise(1)里,我是翻译了一篇文章,里面是探究promise的模式和领略它的魅力。我们可以利用promise,缓解回调函数给我们带来的回调金字塔。使用链式结构书写,使代码更加简洁易懂,易于控制。但是对于构造promise和其内部的实现,却用草草的一句new Promise()就带过。这一次,借着阅读朴灵大神的《深入浅出Node.Js》,我们自己动手实现一个小小的基本的promise吧。

构建Promise对象

首先我们需要回顾一下,一个Promise/A模式和API上是如何定义的:

  • Promise分别有三个状态:pending初始状态,fulfilled完成状态,rejected失败状态。
  • 一旦promise是fulfilled状态或rejected状态,那么它就是不会再改变的。
  • 具备then()方法,用于接收fulfilled和rejected状态的回调方法,并在相应状态下进行触发。
  • then()方法只允许接受function对象,其余的会被忽略。
  • then()方法会返回Promise对象,提供链式调用。
  • then()方法可接收第三个方法,用于支持progress事件的回调方法。

知道我们的Promise对象需要有什么之后,我们就可以开始尝试写Promise的构造函数了。还有Promise是基于事件机制的,也可以说是发布/订阅模式。所以我们为了演示方便,将使用Node里的events模块。

还不清楚自定义事件的同学,推荐一个视频给你们入门:阿当大话西游之WEB组件

var events = require('events'); //events模块
var util = require('util'); //util工具包模块 var MyPromise = function(){
events.EventEmitter.call(this);
};
util.inherits(MyPromise, events.EventEmitter); // 继承 MyPromise.prototype.then = function(resolve, reject, progress){
// this.once()是绑定事件被触发后立即移除事件
if(typeof resolve === 'function'){
this.once('success', resolve);
}
if(typeof reject === 'function'){
this.once('error', reject);
}
if(typeof progress === 'function'){
// 不需要once()
this.on('progress', progress);
}
return this;
};

由此,我们就实现了Promise/A规范。我们用promise对象的then,用相应的事件存放了各个状态的回调函数。那接下来,我们就要知道如何触发这些事件。



构建Deferred对象

为了实现事件的触发,我们需要有一个新的对象Deferred。意思是,延迟对象。

var Deferred = function(){
this.state = 'pending';
this.promise = new MyPromise();
}; Deferred.prototype.resolve = function(obj){
this.state = 'fulfilled';
this.promise.emit('success', obj);
};
Deferred.prototype.reject = function(err){
this.state = 'failed';
this.promise.emit('error', err);
};
Deferred.prototype.progress = function(data){
this.promise.emit('progress', data);
};

我们可以看到,我们之前定义的promise成为了deferred对象中的一个属性。然后Deferred对象的方法,都是用来触发事件来改变promise状态的。这种模式也称作Promise/Deffered模式,它是基于发布与订阅模式,并提供了更加高级的抽象。Deferred对象,用来控制Promise内部,维护Promise状态。Promise对象,则是作用于外部,通过then(resolve, reject)对外提供接口。

对于上一篇讲到的promise化的readJSON,我们可以使用我们定义的Promise/Deferred重写一遍:

var readJSON = function(filename, encoding){
var deferred = new Deferred();
fs.readFile(filename, encoding, function(err, res){
if(err)
return deferred.reject(err);
deferred.resolve(JSON.parse(res));
});
return deferred.promise;
}; // 应用
readJSON('data.json', 'utf-8').then(function(res){
console.log(res.message); // Hello World!
})

对我来说,我更喜欢Promise/Deferred的实现。因为通过Deferred对象,我们可以很随心的控制promise的状态,得到我们想要的样子。当然喜欢原生ES6的那种为Promise构造函数传入工厂函数,也是可以自己改造一下的,和回调参数差不多,可以自行尝试一下。不知道是不是应为我个人水平问题,写起来觉得乱糟糟。所以我更喜欢Promise/Deferred模式的实现。代码如下:

// 去掉Deferred对象,直接通过回调参数来确定是resolve还是reject。
var MyPromise = function(factory){
events.EventEmitter.call(this);
var _this = this;
factory && factory(function(res){
console.log(res);
_this.emit('success', res);
}, function(err){
_this.emit('error', err);
});
};
util.inherits(MyPromise, events.EventEmitter); // 模拟ES6构造函数方法的应用
var readJSON = function(filename, encoding){
return new MyPromise(function(resolve, reject){
fs.readFile(filename, encoding, function(err, res){
if(err)
return reject(err);
resolve(JSON.parse(res));
});
});
}; readJSON("data.json", 'utf-8').then(function(data){
console.log(data.message); // Hello World!
});

我们通过把json解析后传到resolve()中实现了我们上一篇的readJSON函数promise化的要求!酷~

开始使用Promise

从这两篇promise的探究路上,如果能体会到其中的奥妙,应该也差不多可以上道了。这个时候在ES6还未普及前,实现完整优秀promise模式可以借助一些promise库。这里我推荐 Q.js,为了能体现它的高效和优雅,我们借助以往的readJSON例子。

var Q = require('q');
var fs = require('fs'); var readFile = function(filename, encoding){
var deferred = new Q.defer(); // 获取Q的deferred对象 fs.readFile(filename, encoding, function(err, res){
if(err)
return deferred.reject(err);
deferred.resolve(res);
});
return deferred.promise; // 将promise对象return出去,实现链式调用
}; var readJSON = function(filename, encoding){
return readFile(filename, encoding).then(JSON.parse);
}; readJSON('data.json', 'utf-8').then(function(data){
console.log(data.message); //Hello World!
});

这里并没有体现优雅,但是可以看到promise/deferred模式的使用。特别是和我一样喜欢这种模式的同学,简直不能再爽!当然,说到优雅,我们可以回想一下,每次我们处理fs.readFile()callback时,我们都是重复的有错误就reject,没错误就resolve。同样的逻辑,其实我们是可以封装起来的,Q就帮我们做到了这一点。

// 改变fs.readFile()
var readFile = function(filename, encoding){
var deferred = new Q.defer(); // 获取Q的deferred对象
fs.readFile(filename, encoding, deferred.makeNodeResolver());
return deferred.promise; // 将promise对象return出去,实现链式调用
};

语意可得,弄一个Node式的回调。这个实现其实很简单,书上也有说。这个就交给各位同学们,自己动手试一试吧~

接下来,会针对我们自己实现的Promise进行拓展,完成我们更多的需求。在此过程中,探究Promise实现原理。为接下来的异步编程学习打下基础。

再次感谢朴灵老师的《深入浅出Node.Js》。

再次感谢朴灵老师的《深入浅出Node.Js》。

再次感谢朴灵老师的《深入浅出Node.Js》。

重要的话,要说三遍。

异步编程之Promise(2):探究原理的更多相关文章

  1. 异步编程之Promise(3):拓展进阶

    异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...

  2. (翻译)异步编程之Promise(1):初见魅力

    原文:https://www.promisejs.org/ by Forbes Lindesay 异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2) ...

  3. ECMA Script 6_异步编程之 Promise

    Promise 对象 异步编程 方案,已同步的方式表达异步的代码,解决回调地狱的问题 比传统的解决方案——回调函数和事件——更合理和更强大 是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步 ...

  4. 前端异步编程之Promise和async的用法

    传统的异步解决方案采用回调函数和事件监听的方式,而这里主要记录两种异步编程的新方案: ES6的新语法Promise ES2017引入的async函数 Generator函数(略) Promise的含义 ...

  5. JavaScript的异步编程之Promise

    Promise 一种更优的异步编程统一 方法,如果直接使用传统的回调函数去完成复杂操作就会形成回调深渊 // 回调深渊 $.get('/url1'() => { $.get('/url2'() ...

  6. 异步编程之Generator(1)——领略魅力

    异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...

  7. 异步编程之co——源码分析

    异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...

  8. 异步编程之Generator(2)——剖析特性

    异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...

  9. Javascript异步编程之setTimeout与setInterval详解分析(一)

    Javascript异步编程之setTimeout与setInterval 在谈到异步编程时,本人最主要会从以下三个方面来总结异步编程(注意:特别解释:是总结,本人也是菜鸟,所以总结不好的,请各位大牛 ...

随机推荐

  1. How does browsersync work?

    How Does BrowserSync Work? BrowserSync starts a small web server. If you’re already using a local we ...

  2. hdu 4655 Cut Pieces 找规律

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=4655 题意:给你一组整数,代表每个木块所能涂成的颜色种数(编号1~ai),相邻的两块所能涂成的颜色如果是一 ...

  3. Ensemble Learning 之 Bagging 与 Random Forest

    Bagging 全称是 Boostrap Aggregation,是除 Boosting 之外另一种集成学习的方式,之前在已经介绍过关与 Ensemble Learning 的内容与评价标准,其中“多 ...

  4. nodejs开发阶段利器supervisor

    在开始学习nodejs时,往往一般写代码,一边看效果.先停止node,再重新运行.非常耗时. 这时supervisor派上了用场. 安装 推荐使用npm,本人一直使用局部安装,这样可以将全部文件安装在 ...

  5. poj 2127 LCIS 带路径输出

    这个题   用一维 为什么错了: 因为 用一维 dp 方程肯定也是一维:但是有没有想,第 i 个字符更新了 j 位置的最优结果,然后 k 字符又一次更新了  j 位置的最优值,然后  我的结果是  i ...

  6. UESTC 1854

    题目意思  就是说 有一个起点一个终点  和一些虫洞,从一个虫洞进去然后出来,是不需要消耗时间的,注意点就是,虫洞是一条线段,你可以从线段的任意位置进去,从任意位置出来: 所以从一个虫洞到另一个虫洞的 ...

  7. dynamic_cast

    作为四个内部类型转换操作符之一的dynamic_cast和传统的C风格的强制类型转换有着巨大的差别.除了dynamic_cast以外的转换,其行为的都是在编译期就得以确定的,转换是否成功,并不依赖被转 ...

  8. shell script入门

    从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁.用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操 ...

  9. Python学习2-列表和元组

    Python学习2-列表和元组 标签(空格分隔): 列表 元组 在Python中,最基本的数据结构是序列(sequence).序列中的每个元素被分配一个序号--即元素的位置,也称为索引.索引从0开始. ...

  10. C# ComboBox自动完成功能的示例

    先看一下关键代码: DataTable dt = new DataTable(); dt.Columns.Add("Name"); dt.Columns.Add("VV& ...