OurJS


OurJS-我们的JS, 我们的技术-IT文摘; 专注JS相关领域;
我们热爱编程, 我们热爱技术;我们是高大上, 有品味的码农;

欢迎您订阅我们的技术周刊


我们会向您分享我们精心收集整理的,最新的行业资讯,技术动态,外文翻译,热点文章;
我们使用第三方邮件列表向您推送,我们不保存您的任何个人资料,注重您的隐私,您可以随时退订,

欢迎分享您的观点,经验,技巧,心得

让我们一起找寻程序员的快乐,探索技术, 发现IT人生的乐趣;


本网站使用缓存技术每次加载仅需很小流量, 可在手机中流畅浏览;
如果您发现任何BUG,请即时告知我们: ourjs(at)ourjs.com

皇帝的新衣:Node.js


分享到
分类 心得体会   关键字 Node.JS   发布 funnuy  1403189770353
注意 转载须保留原文链接,译文链接,作者译者等信息。  
现在有很多人非难Node.js(例如著名的Node.js is cancer),但是反对者往往误解其中所传达的信息并用一些无关的观点进行反驳。更麻烦的是现在有两类人在使用Node.js,第一类人需要一个高并发的服务器来同时处理大量的连接(例如HTTP代理、Websocket聊天服务器等等),第二类人是重度依赖于JavaScript,他们在浏览器、服务器、数据库甚至洗衣机上都用JS。

我想在这里对那些关于Node.js的奇怪的、有误导性的观点一一进行反驳。

Node.js超级快!


这还不够准确,我们把它分成两个独立的声明:

a. 跑在V8上的JavaScript很快!

V8的开发者值得你去称赞,因为V8让JavaScript快的让人难以置信。多快?从测试比赛上看只比Java慢1到5倍(没错是“慢”)。

如果你仔细看他们的测试,你会发现V8自带了一个很好的正则表达式引擎。结论?Node.js最适合用来完成需要大量正则表达式、CPU繁重的工作。

如果我们把那个测试比赛当作信条,那什么语言/实现通常会比JavaScript/V8快呢?一看,就是一些开发效率很低的语言:Java、Go、Erlang(HiPE)、Clojure、F#、Haskell(GHC)、OCaml、Lisp(SBCL),都是不能用来写服务器的。

更好的是Node.js不需要使用多核,因为解释器是单线程的(评论肯定会说你可以同时跑多个Node.js进程,而其他语言都不可以这么做)。

b. Node.js是非阻塞的!并发性很好!事件驱动!

有些时候,我很怀疑人们是否真的知道他们自己在说些啥。

Node.js在这点甚是奇葩,因为你完全没得到轻量级线程所带来的便捷,而且还要自己完成轻量级线程已经帮你做好的事。因为JavaScript对任何种类的并发都没有直接支持,结果就是一堆使用回调的库函数。编程语言研究者会发现这是蹩脚版的延续传递风格(continuation-passing style (Sussman and Steele 1975)),CPS本来是用来应对递归时栈的增长,不过在Node.js里是应对语言不直接支持并发的问题。

是的,Node.js能在一个线程里高效地处理大量连接,但是它不是第一个也不是唯一个能这样做的运行时系统,看看Vert.x、Erlang、Stackless Python、GHC、Go……

更重要的是,大部分人都用Node.js来实现最小化的可行产品(MVP),因为他们觉得这样可以为未来的大量用户提供一个更快的网站。(当然加载500K的Backbone.js和其他各种各样的库算不上高性能,不过不用介怀的。)

Node.js让并发变的简单!

JavaScript没有内建任何的和并发相关的语言特性,也不支持元编程,Node.js也不能化腐朽为神奇。你只好手工管理全部的延续,或者借助(很多不同的)库来把JavaScript的语法应用到极致。(顺带一提,我觉得shoud.js既可怕又方便。)这就像是现代版本的因为语言里面没有for循环,所以只好用GOTO语句。

来看一下对比吧。

在node.js里,你可能要写这样的函数:

function dostuff(callback) {
task1(function(x) {
task2(x, function(y) {
task3(y, function(z) {
if (z < 0) {
callback(0);
} else {
callback(z);
});
});
});
}
太美了我都不敢看。换Q promise试试:

function dostuff() {
return task1()
.then(task2)
.then(task3)
.then(function(z) {
if (z < 0) {
return 0;
} else {
return z;
});
}
好看多了,但还是很笨。一个副作用:如果你忘记在链式调用的最后加上".done()",Q会把你的异常都吞了,而且还有其他不太明显的问题。当然了,大部分Node.js的库都不用Q,所以你是要老老实实地用回调。如果take2不返回Q promise会怎样?

function dostuff() {
return task1()
.then(function(x) {
var deferred = Q.defer();
task2(x, deferred.resolve);
return deferred;
})
.then(task3)
.then(function(z) {
if (z < 0) {
return 0;
} else {
return z;
}
})
.done();
}
上面的代码是错的,你看出问题了吗?而且,我们还忘了异常处理,修改版:

function dostuff() {
return task1()
.then(function(x) {
var deferred = Q.defer();
task2(x, function(err, res) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(res);
}
});
return deferred.promise;
},
function(e) {
console.log("Task 1 failed.");
})
.then(task3, function(e) {
console.log("Task 2 failed.");
})
.then(function(z) {
if (z < 0) {
return 0;
} else {
return z;
}
},
function(e) {
console.log("Task 3 failed.");
})
.done();
}
错误处理和业务交织在一起,这还有趣吗?

在Go,你的代码会写成这样:

func dostuff() int {
  z := task3(task2(task1())))
  if z < 0 {
    return 0
  }
  return z
}
或者加上错误处理:

func dostuff() int, err {
  x, err := task1();
  if err != nil {
    log.Print("Task 1 failed.")
    return 0, err
  }
  y, err := task2(x);
  if err != nil {
    log.Print("Task 2 failed.")
    return 0, err
  }
  z, err := task3(y);
  if err != nil {
    log.Print("Task 3 failed.")
    return 0, err
  }
  if z < 0 {
    return 0;
  }
  return z;
}
Go版和Node.js版的实现的功能基本等价,除了Go还处理了等待和控制权转让。在Node.js里,我们必须手工管理延续因为我们要和内建的控制流对着干。

噢,在这实现这些东西之前,你还要学会不要错误地使用process.nextTick,不然你的API的用户会很不爽。在这个讲究“精益”和MVP的时代,谁有时间去学这些建立在让人难以理解的运行时上的抽象渗漏问题。

又顺带一提,Q是很慢的(至少网上是这么说的)。看看这个测试,它对比了21种处理异步调用的方式的性能。

难怪人们喜欢Node.js,它给了你轻量级线程的性能以及x86汇编的清晰和可用性。

当人们指出Node.js手工处理控制流很麻烦,反对者就会说:“用函数库去处理,例如async.js”。于是你开始用库函数去并行执行一堆任务或者组合两个函数,这其实就你在任何多线程语言里所做的事,只是更糟糕而已。

 LinkedIn迁移到Node.js,服务器从30台减到3台!

引用Hacker News上的一句:“我把垃圾车换成了摩托,现在我开车快多了!”。

PayPal和沃尔玛换到Node.js之后也得到很好的收益。当然,他们是在对比两个完全不同的东西来让Node.js看起来更好。在他们好到难以置信的故事里,他们从一个庞大的企业级代码库换到一个重头开始写的Node.js应用。这有理由不变快吗?他们换到几乎任何其他东西都会得到性能提升。

在Linkedin的案例里,他们之前的代理都跑在并发度为1的Mongrel上。就像从用1个手指敲QWERTY键盘切换到10个手指敲Dvorak键盘,然后认为这全归功于Dvorak键盘布局更好。

这是一个经典的夸大的广告:真实的故事被误解,扭曲地去让不知情的人产生误解。

Node.js可以再利用你的JavaScript专业知识!


为了更准确,我们也要把它分成两点:

a. 前端开发者也能进行后端开发!

以前JavaScript被用在哪里?主要是浏览器端的前端代码,让按钮加上动画,把JSON变成精美的用户界面等等。在后端用JavaSctipt,你可以你的UI开发者去hack后端关键的网络代码,因为两边都是JS,没什么东西要学的!(对吧?)

直到他们发现不能像平常那样return(因为并发),不能不能像平常那样throw/catch(因为并发),而且全部东西都是基于回调的,会返回Q promise,会返回原生的promise、genator、pipe或其他的奇怪东西,因为这是Node.js。(记得告诉他们要检查类型声明)

你要相信前端开发者学习后端开发的能力。如果不同语言就是一个障碍,那么要明白怎样正确结合各种回调/promise/generator也不是一件简单的事。

 b. 我们可以在前端和后端共享代码!

那你就要把服务器端能使用的语言特性限制到浏览器所支持的特性。例如你的代码不能用JS 1.7的generator,直到浏览器也支持,而且我们也知道等它普及还要好几年。

事实上,如果不远离浏览器的JS,Node的根本就没办法从本质上得到提高。Node.js有很多坑需要用库去填,但是因为它和一个叫JavaScript的语言绑定在一起,它不能直接在语言层面上出处理这些问题。

这是很尴尬的情况,语言本来就没给你到来太多东西,而你又不能改变这个语言,所以你只好一直npm install band-aid。

通过执行某些编译步骤来把新的语言特性转换到旧的语言,这种情况可以得到改善,你也就可以在服务器写新的代码,同时可以运行在正常的JavaScript上。你的选择可能是95%都是JavaScript的新语言(TypeSctipt、CoffectSctipt)或者完全不是JavaSctipt的语言(ClojureSctipt)。

更值得担心的是这意味这你实际上是混淆前端和后台的职责。事实上,你的后台成为了是一个囊括验证、处理等等功能的JSON API,而且这个API会有多个消费者(包括第三方)。例如,当你要造个iPhone和Android应用,你必须决定是用Obj-C、Java或者C#实现一个原生应用还是用Phonegap/Cordova把你的Backbone.js/Angular.js单页面应用包装起来。根据不同的部署平台,这时在服务器和客户端共享的代码可能会成为不利因素。

NPM很好用!


我觉得NPM已经到了一个“不坏”的状态,这已经领先于很多包管理工具。就像是大多数的生态系统,NPM上有多个实现同样功能的包。例如你需要一个库来向Android推送通知。在NPM,你能找到:gcm、node-gcm、node-gcm-service、dpush、gcm4node、libgcm和ngcm,更不用提那些支持多个推送服务的库。哪个可靠?哪个已经停止开发?最后,你选了下载量最大的那个(为什么结果不能按流行度排序呐?)。

NPM过去经常宕机,看着很多公司突然间就不能部署代码也是一件好玩的事。现在NPM上线时间已经好了很多,但是谁知道它会不会打断你的部署进程

过去,我们成功部署代码而且不用在部署阶段引入对一个新生的、由志愿者运行的、从头实现的仓库的依赖。我们甚至在本地保存一份函数库的代码。

我倒不担心NPM,某种程度上它是生态系统的一部分而不是语言的一部分,而且通常情况都能满足要求。

用Node.js时我效率更高!敏捷!快速!MVP!

似乎Node.js程序员心里都有一个奇怪的二分法:你在用mod_php或者可怕的JavaEE,所以是又大又慢的;你在用Node.js,所以是精益和快速的。这可能就是为什么你很少看到有人吹他怎样从Python换到Node.js*。自然,如果你来自一个到处都是AbstractFactoryFactorySingletonBean的过度工程的系统,Node.js的缺乏结构反而是清新的。但只是因为这样就说Node.js更高效是错误的——因为他们无视了全部的坑。

一个Node.js新人可能会这么做:

  1. 这个函数可能失败,我要抛一个异常,所以我会写 throw new Error("it broke");
  2. 那个异常没有被我的try-catch捕获!
  3. process.on("uncaughtException")又好像可以
  4. 但是得到不是想象中堆栈轨迹,StackOverflow说这可能违背了最佳实践
  5. 也许我要试一下domain?
  6. 哦,回调通常以错误作为第一个参数,我要回去改改我的函数调用
  7. 有人告诉我应该用promise
  8. 把例子看了十来二十遍,我觉得应该可以了
  9. 不过它还是吃了我的异常。不,我还要在最后加上.done()

一个Python程序会这么做:

  1. raise Exception("it broke");

这是一个Go程序员:

  1. 我要把err加到返回类型声明,还要在return语句加一个返回值

Noed.js中有很多东西会阻碍你实现MVP。MVP不是担心能不能快40ms返回一个HTTP响应或者你的DigitalOcean机子能同时处理多少连接。你没有时间去成为一个并发编程的专家(而且明显你现在也不是,如果是的话你就不会用Node了)。

* 这里有一篇关于从Python换到Node.js的文章。最有价值的一句:“这种推迟的编程模式更难理解和调试。如果开发者不能完全理解Twised,他就会犯很多无知的错误,最后会死的很惨”。所以他们换到另一个困难的系统,如果你不能理解它然后又犯了些无知的错误,它一样会微妙地挂掉。

我热爱Node.js!Node.js就是生活!


如果你组织Node.js活动时需要演讲者,我随时都进行付费的演讲演出。详细请用邮箱联系我。

这些观点不代表也我的公司和同事,也没有经过他们的复审。你也可以把全部文字都加上<sarcasm>标签。

(注* 感谢funnuy的分享)

原文地址: 点此
社区评论 ( Beta版 )
OnceDoc 您自己的企业内容管理系统——文档、流程、知识库、报表、网盘All In One

访问404页面,寻找丢失儿童
 热门文章 - 分享最多
  1. 我不想雇佣女性
  2. 如何兼职创业并避免风险
  3. DevOps:全能开发是如何扼杀程序员的
  4. PHP vs Node.js:真正的评测数据
  5. 沃尔玛为什么要采用Node.js
  6. Swift的前世今身-创始人的自述
  7. JavaScript中NaN的秘密
  8. 在JavaScript中创建命名空间的几种写法
  9. Intel: Javascript将全面支持SIMD
  10. jQuery:在一个回调中处理多个请求
  11. AirJD-简单好用的免费建站工具

 相关阅读 - 心得体会
  1. “设计师思维”正在毁灭我们
  2. 现在,你为什么应该学Node.js
  3. 7件你不知道但可以用CSS做的事
  4. NODEJS是人性化的
  5. 用JavaScript的5个原因
  6. 告诉你为什么要合并样式文件的另一个原因
  7. 干嘛不在企业中使用Node.js呢?
  8. 你用什么工具开发JavaScript?
  9. 一系列JavaScript的基础工具
  10. 为什么TypeScript解决不了问题

 关键字 - Node.JS
  1. Node.JS用Path将相对路径转为绝对路径
  2. 使用Node.JS监听文件夹变化
  3. Node.JS循环删除非空文件夹及子目录下的所有文件
  4. Node.js网页抓取:一个最简单的http请求客户端示例(request client)
  5. 如何用Bootstrap免费网站模板和OnceAir云盘搭建个人网站服务器
  6. Node.JS如何查看本地MAC/IP地址、计算cpu使用率和内存容量
  7. Express入门教程:一个简单的博客
  8. 在nodejs中使用Redis缓存和查询数据及Session持久化(Express)
  9. 如何收集捕获Node中的未处理的错误异常信息?
  10. trim-html:NodeJS的HTML文本截取库(自动生成正文摘要)

 欢迎订阅 - 技术周刊

我们热爱编程, 我们热爱技术; 我们是高端, 大气, 上档次, 有品味, 时刻需要和国际接轨的码农; 欢迎您订阅我们的技术周刊; 您只需要在右上角输入您的邮箱即可; 我们注重您的隐私,您可以随时退订.
加入我们吧! 让我们一起找寻码农的快乐,探索技术, 发现IT人生的乐趣;


 关注我们

我们的微信公众号: ourjs-com
打开微信扫一扫即可关注我们:
IT文摘-程序员(码农)技术周刊

ourjs官方微信号