2月阅读周·深入浅出的Node.js | 有异步I/O,必有异步编程
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效。
已读完书籍:《架构简洁之道》。
当前阅读周书籍:《深入浅出的Node.js》。
异步编程
函数式编程
函数的灵活性是JavaScript比较吸引人的地方之一,无论调用它,或者作为参数,或者作为返回值均可。
高阶函数
函数的参数一般接受基本的数据类型或是对象引用,返回值也是基本数据类型和对象引用。
高阶函数则是可以把函数作为参数,或是将函数作为返回值的函数。
如下代码所示,将函数作为了返回值:
function foo(x) {
return function () {
return x;
};
}
偏函数用法
偏函数用法是指创建一个调用另外一个部分——参数或变量已经预置的函数——的函数的用法。
在下面的代码中,通过isType()函数预先指定type的值,然后返回一个新的函数:
var isType = function (type) {
return function (obj) {
return toString.call(obj) == '[object ' + type + ']';
};
};
var isString = isType('String');
var isFunction = isType('Function');
异步编程的优势与难点
优势
Node带来的最大的优势是基于事件驱动的非阻塞I/O模型,非阻塞I/O可以使CPU与I/O并不相互依赖等待,让资源得到更好的利用。
难点
1、难点1:异常处理
异步方法通常在第一个阶段提交请求后立即返回,因为异常并不一定发生在这个阶段,try/catch的功效在此处不会发挥任何作用。
Node在处理异常上形成了一种约定,将异常作为回调函数的第一个实参传回,如果为空值,则表明异步调用没有异常抛出:
async(function (err, results) {
// TODO
});
2、难点2:函数嵌套过深
Node中,事务中常存在多个异步调用的场景。
如下代码,虽然结果的保证上是没有问题的,但是这样的实现并没有利用好异步I/O带来的并行优势。
fs.readFile(template_path, 'utf8', function (err, template) {
db.query(sql, function (err, data) {
l10n.get(function (err, resources) {
// TODO
});
});
});
3、难点3:阻塞代码
JavaScript中没有sleep()这样的线程沉睡功能,用于延时操作的只有setInterval()和setTimeout()这两个函数。但这两个函数并不能阻塞后续代码的持续执行。
通常开发者会采用如下的方式达到阻塞的效果:
// TODO
var start = new Date();
while (new Date() - start < 1000) {
// TODO
}
// 需要阻塞的代码
但是实际上这段代码会持续占用CPU进行判断,与真正的线程沉睡相去甚远,完全破坏了事件循环的调度。
4、难点4:多线程编程
对于服务器端而言,如果服务器是多核CPU,单个Node进程实质上是没有充分利用多核CPU的。
浏览器提出了Web Workers,它通过将JavaScript执行与UI渲染分离,可以很好地利用多核CPU为大量计算服务。同时前端Web Workers也是一个利用消息机制合理使用多核CPU的理想模型。借助Web Workers的模式,开发人员要更多地去面临跨线程的编程。
5、难点5:异步转同步
Node提供了绝大部分的异步API和少量的同步API,偶尔出现的同步需求将会因为没有同步API让开发者突然无所适从。目前,Node中试图同步式编程,但并不能得到原生支持,需要借助库或者编译等手段来实现。
异步编程解决方案
异步编程的主要解决方案有如下3种:
事件发布/订阅模式
事件监听器模式是回调函数的事件化,又称发布/订阅模式。Node自身提供的events模块是发布/订阅模式的一个简单实现,Node中部分模块都继承自它,这个模块比前端浏览器中的大量DOM事件简单
Promise/Deferred模式
Promise/Deferred模式,是一种先执行异步调用,延迟传递处理的方式。
Promise/Deferred模式在一定程度上缓解了深度嵌套让编程的体验变得不愉快的问题。
流程控制库
一些非模式化的应用,虽非规范,但更灵活。
1、尾触发与Next
尾触发方法是需要手工调用才能持续执行后续调用的,常见的关键词是next。尾触发目前应用最多的地方是Connect的中间件。
2、async
async,流程控制模块。在Node开发中,流程控制是开发过程中的基本需求。async模块提供了20多个方法用于处理异步的各种协作模式。
3、Step
流程控制库Step,它比async更轻量,在API的暴露上也更具备一致性,因为它只有一个接口Step。
4、wind
wind,前身为Jscex。它为JavaScript语言提供了一个monadic扩展,能够显著提高一些常见场景下的异步编程体验。
异步并发控制
在Node中,可以利用异步发起并行调用。
对于异步I/O,虽然并发容易实现,但是依然需要控制。需要给予一定的过载保护,以防止过犹不及。
异步调用的并发限制在不同场景下的需求不同:非实时场景下,让超出限制的并发暂时等待执行已经可以满足需求;但在实时场景下,需要更细粒度、更合理的控制。
总结
我们来总结一下本篇的主要内容:
- 异步编程的优势是基于事件驱动的非阻塞I/O模型,非阻塞I/O可以使CPU与I/O并不相互依赖等待,让资源得到更好的利用。
- JavaScript异步编程的难题已经基本解决,无论是通过事件,还是通过Promise/Deferred模式,或者流程控制库。
- 在掌握以上技巧之后,异步编程不是难事,习惯异步编程之后,将会收获许多值得享受的编程体验。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)