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

异步编程系列教程:

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

拓展功能


在前面的文章中,通过了解promise能做什么,实践动手从原理上了解promise/deferred模式的用法,相信大家应该更期待这次的功能拓展。我们不仅需要让单异步操作promise化,我们还需要从实际出发,拓展更多有用的功能。直接看一下我们这一次需要做的两个功能:

  1. 多异步并行控制
  2. 多异步串行队列

这两个功能用我们之前自己写的简陋promise库,是无法做到的。我们不能在指定多个promise异步完成后,再触发回调。也不能让多个promise异步像排队一样,一个一个的进行,甚至下一个promise的参数是依赖上一个promise的。这就是我们接下来需要解决的问题:

多异步并行控制

在冻手之前,我们先想一想大致的思路吧。首先我们肯定是并发了多个异步,我们需要做的仅仅就是,监控所有并发的异步,并让最后一个异步触发resolve回调函数。当然错误处理的话,就是当有一个异步错误,直接就reject掉宣布异步失败结束。一般监视并发,我们都会有一个哨兵变量,每完成一个异步,就对哨兵进行维护并检测异步是否结束。

那我们的API应该怎么设置呢?朴灵老师的书上是这样的:deferred.all([promise1, promise2]).then()。从这里我们可以看出,就是由各个小promise组成了一个大的promise,并在大promise中进行接下来的操作。一起看一下代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Deferred.prototype.all = function(promises){
var result = []; // 存储各个promise的执行结果
var count = promises.length; // 哨兵变量
var _this = this;
promises.forEach(function(promise, index){
promise.then(function(res){
result[index] = res;
count--;
// 当执行最后一个promise后, 调用大promise的resolve,并把result传进去
while(count === 0){
_this.resolve(result);
}
}, function(err){
// 有一个promise出错,立即return并执行大promise的reject
return _this.reject(err);
});
});
return this.promise;
};

我个人认为最不好懂的应该是_this到底指的是什么?看过上一篇的朋友,应该知道deferred是延迟对象来的,作用就是触发即将在then()中绑定的resolve()reject()。那这里的_this必然是指大的promise,我们看一下如何使用的:

阅读全文 »

异步编程之Promise(2):探究原理

异步编程系列教程:

  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组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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,用相应的事件存放了各个状态的回调函数。那接下来,我们就要知道如何触发这些事件。

阅读全文 »

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

原文:https://www.promisejs.org/

by Forbes Lindesay

异步编程系列教程:

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

动机


思考一下,下面这段用来读取文件并解析JSON的Javascript同步代码。它很简单并且易于阅读,但是因为它会阻塞代码,你并不会想用在大多数的应用里。这意味着,当你用它来读取文件的时候(它需要很多时间)不会有其他的事情发生。

1
2
3
4
function readJSONSync(filename){
// 读取文件后,再解析成JSON
return JSON.parse(fs.readFileSync(filename, 'utf-8'));
}

为了让我们的应用高性能且实时响应,我们需要让所有涉及到IO的操作都变成异步的。最简单的方法去实现它就是使用callback回调。然而,一个幼稚不成熟的代码实现也许会让它出错。

1
2
3
4
5
6
7
8
function readJSON(filename, callback){
fs.readFile(filename, 'utf-8', function(err, res){
if(err)
return callback(err);
// 回调参数为,错误null和文件解析后的JSON
callback && callback(null, JSON.parse(res));
})
}

出现的问题:

  • 额外的callback回调参数会使我们困惑,不知道变量到底是输入值还是返回值。
  • It doesn’t work at all with control flow primitives.(这里我无法理解控制什么流呢?)
  • 无法处理由JSON.parse抛出的错误。

我们需要处理由JSON.parse抛出的错误,但是我们同样也需要小心不要影响到了由callback函数抛出的错误。最后我们用一堆混乱的错误处理完成了:

1
2
3
4
5
6
7
8
9
10
11
12
function readJSON(filename, callback){
fs.readFile(filename, 'utf-8', function(err, res){
if(err)
return callback(err);
try{
res = JSON.parse(res);
} catch(ex){
return callback(ex);
}
callback(null, res);
});
}

尽管有这些杂乱代码来处理错误,我们仍然留下一个问题就是callback烦人的回调参数。Promise可以帮助你更自然的处理错误,没有callback的参数使代码更简洁,并且用不着修改底层的结构(意思是你可以用原生Js来实现promise,并且用它来封装已经存在的异步操作)

阅读全文 »

记拿到鹅厂前端开发暑期实习offer的经历

想起来时的路


在真正拿到腾讯实习offer之前,也是看过不少人的面经,心生向往。很早在入前端坑之前,我就想着大四的时候有机会要尝试去腾讯里实习。

大一入门语言就是C++,这让我很无奈,所以我很快的就抛下忘记了,留下的只是一堆基本编程和它的面向对象的概念。那会儿我喜欢听摇滚乐,民谣,组乐队,玩音乐。其实玩音乐的人理想才是最让人崇拜的,那种自由,那种对生活的追求。可是我知道自己并不能上道,于是学会了吉他和一丢丢的架子鼓,就开始回到了编程学习。选前端真是无意中的,我只是随手买了一本HTML5的书,然后觉得CSS也不错,最后才知道有Javascript这个东西。后来随着逐步的深入,已经开始心生喜欢。

我知道有很多书都是骗钱的,于是我去豆瓣,去亚马逊认认真真比较。买了我第一本最重要的书《Javascript高级程序设计》。接下来的整个暑假,我在没有网络的新家里,每日每夜琢磨书上的知识点,因为它真的很奇葩。后来接触到知乎,自己的前端水平的深度和广度的视野也就跟着上来了,就开始知道前端这水非常深。你现在还可以在百度贴吧,看到一堆人求各种JQuery插件,或者一堆问怎么学有没有前途,却始终不肯开始行动的人。
我开始知道了前端要解决各种以前从来不会有的问题和各式各样的新技术。例如:

阅读全文 »

Javascript事件处理进阶

这篇文章是我在看乌龟书《编写可维护的Javascript》发现的一篇写的非常好的章节,在这里我并不会教大家什么是绑定事件等比较基础的事。有兴趣了解DOM事件的同学们,可以去w3cschool查阅。

事件绑定


大家都知道前端开发中,事件处理是非常重要的。我们在view层的交互,都是通过绑定事件到UI上,然后我们再处理这些事件。让我们写一个事件绑定的函数先。

1
2
3
4
5
6
7
8
9
10
11
12
var addListener = function(target, type, listener){
if(target.addEventListener){
//2级的DOM事件绑定
target.addEventListener(type, listener, false);
}else if(target.attchEvent){
//IE事件绑定
target.attchEvent("on" + type, listener);
}else{
//0级的DOM事件绑定,也是最可靠的,但只可单次绑定
target["on" + type] = listener;
}
}

上面写了一个兼容性强的DOM事件绑定,但希望大家要注意:DOM0级的事件只可单次绑定,再绑定会导致覆盖的问题出现。

常见用法


当事件触发后,会有一个event回调参数传入事件处理中。而所有的有关事件的信息都会储存在event对象中。我们假设需要做一个点击,并在鼠标的位置弹出框。我们是这样写的。

1
2
3
4
5
6
7
8
var handlerClick = function(event){
var popup = document.getElementById("popup");
popup.style.left = event.clientX;
popup.style.top = event.clientY;
popup.className = "active";
}

addListener(element, "click", handlerClick);

乍一看,这是没什么问题的。确实它可以运行的很好,这毋庸置疑。可是你是否有考虑过,你的事件处理程序中,却包含了与用户行为无关的应用逻辑,也就是指弹框这个行为。或许当我们使用mousemove事件时,也需要这段应用逻辑,并可以为此单独拓展一些行为时,我们就只能通过copy同样的应用逻辑并在里面添加自己需要的代码。

而另一个同样明显的缺点,则是关于测试的。如果测试需要通过模拟触发事件的形式进行的,现有的测试框架并不是特别稳定和理想。而将隔离出应用逻辑,可以使我们直接触发功能代码。

阅读全文 »