再见2015

2015年将要过去,此博客也维护了近10个月,近来工作比较忙,回家后还要分给小公举一部分时间,已经无法像从前一样回家后有大把时间研究技术。

最近几篇文章,质量已经有所下降,几乎只能挤出时间去玩弄点新技术,而无暇去写文章了,似乎也只是借口。

react也断断续续研究了有个把月了,虽然没有在正式项目中使用,但在个人把玩中也略微能体验到其独到精髓之处。今晚加入了immutable之后(https://github.com/stoneChen/myReact),打算对react的研究,告一段落。

原本打算在公司内大力推react,但苦于公司前端整体水平欠佳,大部分人比较难上手,此想法也被扼杀在摇篮中。但是,却发生了一件『无心插柳』的事情。早前也偶尔听说过vueJS,仅有的印象便是小清新,支持双向绑定。大概不到1个月前,公司来了个新项目,正纠结技术选型,突然想起了vueJS,就建议一个蛮有实力的小伙伴去尝试一下。经过一个礼拜的探索,竟也毫不费力的搭建出了一个前端架构出来,然后几个初级前端也加入进来,几天下来,也能勉强上手了!vueJS还有一点对国人比较友好的是,作者是一名华侨(尤大),官网有中文版的!这让很多英文不好的同学,也能尽快学习入门~

vueJS轻量,支持双向绑定,社区也不算冷,很多大牛们也蛮看好,预测在2016年会有大的增长趋势。接下来我也要加入vueJS的浪潮中~说了这么多,其实我还没敲过一行关于vueJS的代码囧,只是读过一些关于它的文章,和官网的一些文档,感觉还是很好上手的,尤其是用过angularJS的同学,就从2016的元旦开始!

虽说就要投入vueJS的怀抱了,不过react native也放下好久了,公司正好准备搞一款新的ios App,心里还是蛮痒痒的。

且行且看吧。

新的一年,怕是没有那么多时间可以写文章了,更是做不到每周一更了,这一篇就已经断更一次了。只能说,尽力而为吧,多攒点干货一起发,好过每周都是不痛不痒的文字。

再见2015!

忙里偷闲把玩webpack

这周各种通宵加班。。。有时候也只是处理等待状态,不久的将来工作上需要频繁的开发静态页面,于是想到搭建一个快速开发环境,于是想到了webpack这个神器。

之前也只是在研究react的时候,用了一下webpack,但也仅仅是入了门。这次有机会从零开始基于webpack搭建开发环境,等基本功能完毕后,又基于yeoman开发了一个非常非常简单的搭建上述开发环境的脚手架,项目地址: https://github.com/stoneChen/generator-xpage

也没有用到webpack太高级的功能,前端类库,无非就是jquery,lodash,fastclick.CSS预处理器选择了stylus,less用了很久了,有点审美疲劳囧,sass的node-sass安装太慢太慢。。。gulp用于清理dist目录,其实有点大材小用哈,不过说不定以后会用它来做更多的事情呢。

然后今天下午开始基于 这个篇文章 提到的目录结构,重构之前的react demo,期间卡在reducers的import好久,后来终于纠结完毕后,最后无法忍受视图与reducer和action分离之苦,放弃,只是简单重命名了之前的某些目录并作微调。

angular中的$q小结

近日,公司小伙伴把angular的$q服务,运用的炉火纯青,登峰造极,遂对之做个小结。

名词解释:

resolveHandler:then方法的第一个参数

rejectHandler:then方法的第二个参数或catch方法的第一个参数(catch方法即为then(null, function)的简写)

先来个基本用法:

1
2
3
4
5
6
7
8
var p = $q(function (resolve, reject) {
setTimeout(function () {
resolve('success');
}, 100)
});
p.then(function (data) {
console.log('then1:', data);
});

结果会输出

1
then1:success

这应该很好理解。

然后再来一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
var p = $q(function (resolve, reject) {
setTimeout(function () {
resolve('success');
}, 100)
});
p.then(function (data) {
console.log('then1:', data);
});
p.then(function (data) {
console.log('then2:', data);
});

结果会输出:

1
2
then1: success
then2: success

再看这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
var p = $q(function (resolve, reject) {
setTimeout(function () {
resolve('success');
}, 100)
});
p.then(function (data) {
console.log('then1:', data);
return 'haha'
})
.then(function (data) {
console.log('then2:', data);
});

结果却是:

1
2
then1: success
then2: haha

两者非常相似,结果却不一致,为什么呢?

前者属于『分发式』调用(我自己YY的),『分发』后的resolveHandler的入参是分发者的返回值,即

后者这种属于链式调用,后一个then的resolveHandler的入参就是上一个then的resolveHandler的返回值, 即

再看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var p = $q(function (resolve, reject) {
setTimeout(function () {
resolve('success');
}, 20)
});
p.then(function (data) {
console.log('then1:', data);
return 'haha'
})
.then(function (data) {
console.log('then2:', data);
return $q.resolve('xixi');
})
.then(function (data) {
console.log('then3:', data);
return $q(function (resolve, reject) {
setTimeout(function () {
resolve('success again')
},20)
})
})
.then(function (data) {
console.log('then4:', data);
})

输出结果为:

1
2
3
4
then1: success
then2: haha
then3: xixi
then4: success again

如果上一个then的resolveHandler返回的是一个promise对象,那么下一个then的resolveHandler的入参为这个promise对象resolve的值.

为了印证上述结论,再看一个更复杂的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var getPromise = function (resolveValue) {
return $q(function (resolve, reject) {
setTimeout(function () {
resolve(resolveValue);
},20)
})
};
getPromise('p1')
.then(function (data) {
console.log('then1:', data);
return 'haha'
})
.then(function (data) {
console.log('then2:', data);
return $q.resolve('xixi');
})
.then(function (data) {
console.log('then3:', data);
return getPromise('p2').then(function (data) {
console.log('then4:', data);
return getPromise('p3');
})
})
.then(function (data) {
console.log('then5:', data);
})

输出结果为:

1
2
3
4
5
then1: p1
then2: haha
then3: xixi
then4 p2
then5: p3

OK,上述都是resolve的情况,下面看看reject的情况。

来一个基本的:

1
2
3
4
5
6
7
8
9
10
$q(function (resolve, reject) {
setTimeout(function () {
reject('failed');
}, 20)
})
.then(function (data) {
console.log('success1:', data);
},function (data) {
console.log('fail1:', data);
});

结果输出:

1
fail1: failed

应该很好理解。

来看一个复杂的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$q(function (resolve, reject) {
setTimeout(function () {
reject('failed');
}, 20)
})
.then(function (data) {
console.log('success1:', data);
})
.then(function (data) {
console.log('success2:', data);
},function (data) {
console.log('fail2:', data);
return 'fffff';
})
.then(function (data) {
console.log('success3:', data);
return $q.reject('reject 3');
}, function (data) {
console.log('fail3:', data);
})
.then(function (data) {
console.log('success4:', data);
})
.catch(function (data) {
console.log(data);
});

输出结果为:

1
2
3
fail2: failed
success3: fffff
reject 3

promise被reject后,第一个rejectHandler将接受到reject的值,但是这个rejectHandler返回的值就此『迷途知返』,传给接下来的resolveHandler。如果返回的是将被reject的promise对象,被reject的值才会被传递给下一个rejectHandler。

 

总结

似乎讲的并不是那么清楚,请读者仔细推敲代码的运行结果,可以更加深刻理解$q的用法,用于处理复杂异步逻辑非常合适。本文没有涉及$q的所有API,而且promise规范也不仅仅只有一种,请读者自行延伸查阅。

内网web服务穿透微攻略

虽然这些天经常加班,但有时候加班处于待命状态,所以抽空研究了下内网穿透,即从外网访问内网web服务。

本来想想这种事情除了开VPN,似乎没有别的办法,直到前段时间一同事告诉我他通过ssh隧道实现了这个需求,并发给我一篇英文博客,当时只是随便瞄了几眼文章里的图,没有时间去看。于是后来断断续续的暗暗研究,由于英语不好,都是搜索中文文章,但看了好多篇文章,都是单单讲SSH,没有涉及到HTTP,没有说到我需求的关键点。直到最后,再回头看看前面那篇英文博客,一切都豁然开朗了!

那篇文章在这 https://chamibuddhika.wordpress.com/2012/03/21/ssh-tunnelling-explained/ (请自备梯子)

好了,下面开始进入正题。由于是『微攻略』,只涉及到操作的步骤,不讲具体原理。

看了好多篇文章,ssh隧道需要借助中间主机(外网需可访问)进行端口转发,而且分为本地转发和远程转发,我们这里需要的是远程转发。

核心命令,即在内网主机执行:

ssh -C -f -N -R *:{中间主机监听端口}:localhost:{内网主机监听端口}{中间主机用户名}@{中间主机} [-p {中间主机ssh端口}]

举个栗子,假设:

内网主机:192.168.100.101

中间主机:123.123.123.123(ssh端口为333,一般默认为22)

需要从外网访问内网主机的web服务端口:8080

希望中间主机监听的端口:2222

登陆主机的用户:root

那么在内网主机上需要执行的命令为:

1
ssh -C -f -N -R 2222:localhost:8080 root@123.123.123.123 -p 333

然后会提示输入中间主机的用户密码,输入完毕后,则建立ssh隧道完毕。

此时在中间主机执行:

1
curl localhost:2222

就可以看到打印出内网主机返回的http信息。

至此核心部分已经完成了,还剩下几个问题:

  1. 第三方主机(HTTP客户端)尚无法通过中间主机访问内网web服务
  2. 每次执行此命令还需要输入中间主机的用户密码
  3. 即使是守护进程中运行的,也会在一段时间后断开连接
    我们一个个解决:

1.需要开启中间主机的GatewayPorts:

通过vi /etc/ssh/sshd_config 文件,将 GatewayPorts 设置为 yes,保存退出vi。然后执行

1
/etc/init.d/sshd restart

这是centos的命令,其他linux版本可能有所不同
然后就可以在家里用自己电脑,访问内网的WEB服务了!比如在浏览器里打开http://123.123.123.123:2222/something就能看到内网的web页面了~

2.把内网主机的ssh公钥添加到中间主机的相应用户的~/.ssh/authorized_keys中,甚至可以设置中间主机别名

3.通过autossh(一个命令行工具),当隧道断开连接后,可自动重连,通过包管理工具安装好后,只需将上述命令的ssh替换为autossh即可,比如上面的例子,改为autossh就是:

1
autossh -C -f -N -R 2222:localhost:8080 root@123.123.123.123 -p 333

好了,接下来就可以愉快的玩耍了~

本周停更通知

这周末老家来了三个亲戚,陪了近两天,而且前几天一直忙于公司的一个紧急项目,暂时没有时间也没有干货可分享(抑或没来得及整理),见谅。

react-webpack雏形诞生

请了一个礼拜的陪产假,6天多没碰电脑,最后一个晚上再次拿起react,又弄了一个demo,然后这两天继续改进,终于可以非常勉强的拿出来分享一下,当然还有很多不足,后续会持续改进。最大的一个不足(或者叫缺失更合适囧)是,还没有加入Ajax的支持,下一步先解决这个问题,工程化还有很多优化空间。

地址: https://github.com/stoneChen/myReact

react

DIY使用Node代理HTTP请求

近日,为了更加方便前后端联调,响应组里小伙伴的需求,为grunt工作流中的connect任务增加代理中间件,将非静态资源请求转发到测试环境或后端本地服务。

google了一番,找到了 proxy-middleware 这个模块,正好符合我的需求,使用很简单,只需在connect的中间件数组里添加一项即可:

1
2
3
4
5
6
7
8
9
10
var doProxy = require('proxy-middleware');
//...other code
connect: {
livereload: {
options: {
middleware: function (connect) {
return [
//其他中间件
connect().use('/', doProxy('http://example.com'))
]

后来,为了在服务启动过程中可根据配置文件代理host的不同值,实时代理不同host,改为在中间件里读取yml文件配置的host:

1
2
3
4
5
6
7
8
9
//在靠后的中间件里
//每次请求都读取代理host配置,若配置了该值,则代理到这个host,若未配置,则使用本地mock
var apiRoot = envCfg.proxy;
if (apiRoot) {
grunt.log.writeln('apiRoot found, dispatch to `' + apiRoot + '`');
return doProxy(apiRoot)(req, res, next);
} else {
grunt.log.writeln('apiRoot NOT found, use local mocking');
}

往github上推了一个 demo

完成了这个需求后,读了一下 [proxy-middleware](https://github.com/andrewrk/node-proxy-middleware) 的源码,里面包含了对http头的各种处理,包括cookie,主要通过一个新的http/https模块的request对象,结合源request对象中的http头(包括cookie),向指定host发起一个新的http请求,在响应回调中,将代理host 的响应pipe给源响应(添加了少数头字段),完成了整个代理的过程。

回头想想,之前书上看过的HTTP知识这次真的派上用场了,正好《深入浅出NodeJS》快看完了,作者说Node可以帮助我们更加底层的了解HTTP原理,真的是这样!反观早前从事的JAVA开发,那框架封装的简直了,根本难以理解HTTP的运作原理。赞Node!

《深入浅出NodeJS》这本书对Node底层的介绍偏多,感觉实战还远远不够,还应该再来一本。

昨晚『开发者头条』推送了一条『百度母婴技术团队基于 ReactJS 实现 webapp』的消息,瞬间又激动了,里面提到的整个架构,与我之前所想大都相符,抓紧时间整出一套可行的方案!