玩node的同志们都知道,当这门语言被提出来的时候,作为自己最为骄傲的异步机制,却被PHP和Python等战团喷得不成样子的是,他们嘲笑着nodejs那蠢蠢的无限嵌套,nodejs战团只能以我们只要性能!!!来安慰自己。

众所周知,javascript作为一个单线程语言,所有工作都是阻塞的,有好多人不理解为什么说是javascript是阻塞的,怎么可以做到异步机制呢?

举一个栗子

在我们平时可以接触到的情况下,我们可以用浏览器来触发XMLHttpRequest(Ajax)来异步获取数据,setTimeout、setInterval来完成定时任务,而这并不是javascript的语言来决定这些异步操作的,而是解释Javascript的浏览器来去操作线程作多线程操作的,可以把这些方法理解为浏览器抛出的多线程API。而nodejs是基于高性能v8来实现,它也是像浏览器一样,抛出了很多操作线程的API,从而来实现异步机制

异步的机制可以让我们更为节省系统资源,并不需要为每一个请求去像PHP,Tomcat一样新开一个线程,node内部会有处理各种任务的线程(使用Net,File System,Timers 等很多模块来操作不同的线程),把不同的异步任务分发给各个任务线程,并会弹性地为线程分配硬件,这都是来自v8的高性能,也是为什么nodejs能面对高I/O情况的根本原因。

现实


到头来我们必须面对血淋淋的现实,当我初接触node的时候,代码也是这样写的

fs.readFile(MrFileFirst,"utf8",function(err,data1){
if(err){
//do err thing
}else{
fs.readFile(MrFileSecond,"utf8",function(err,data2){
if(err){
//do err thing
}else{
mongo.find(SomeQuery,function(err,data3){
if(err){
//do err thing
}else{
//do the real thing with [data1,data2,data3]
}
})
}
})
}
})

Oh,my god!好好的异步机制还是玩成了同步……而且惨不忍睹!仅仅只是想返回最后的三个数据,但是这个例子三个任务之间并没有关系嵌套,这样子强行把异步玩成同步的话,还是阻塞的代码,这段代码的工作时序大概在这样的:

和不用node并没有什么区别,完全是阻塞的。在平时我们可以碰到更多的关系层级的嵌套(下一步的操作要基于上一步的结果),这时才必须使用同步去完成任务,但是要是像上面这样写的话,我相信你会写到吐血的(我已经忘了我在代码中写过多个少if (err) {}了,因为node的底层API异步方法都是以err为第一个参数,使得上层所有异步方法都为这种模式)

进化


有人看不下去了,便自会有人站出来,我们渐渐地实现了从无到有的过程,我最开始接触的是阿里的

eventproxy

var ep = require("eventproxy");

ep.create("task1","task2","task3",function(result1,result2,result3){
//do the real thing with [result1,result2,result3]
}).fail(function(e){
//do err thing
}); fs.readFile(MrFileFirst,"utf8",ep.done("task1"));
fs.readFile(MrFileSecond,"utf8",ep.done("task2"));
fs.readFile(MrFileThird,"utf8",ep.done("task3"));

这样,就可以实现三个文件异步进行读取,并且在三个任务都完成时进行最终的工作,时序图如下图:

三个任务几乎同时触发(除去代码的触发时间),所以左边的三个点其实可以看作是一个点,而这三个任务都去同时异步进行,在三个任务都完成的时候,来触发最后的任务。

这才是node发挥出自己优点的地方,处理时间节省了很多(如果三个任务的时间消耗都为1,则时间缩减了2/3),这才是大node.js

eventproxy也有更多的用法,可以去其npm上看看。

async

async是国外强大的异步模块,它的功能与eventproxy相似,但是维护速度与周期特别快,毕竟是用的人多呀,但是支持国产——是一种情怀,附介绍使用async的文章

http://blog.fens.me/nodejs-async/

再进化


人总是不知足的,而刚好是这个不知足,才让我们不停地去探索想要的、更为方便的东西。而这时,便有人想让自己写的代码复用性更高,同时也不想去写那么多的callback去嵌套,这时便有了Promiss/A+规范,其是:

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.

一个健全的通用JavaScript Promise开放标准,源于开发者,并归于开发者

在ES6中也新增了原生Promise的使用,而之前Promise库有promise,Q,bluebird等,在这些库中现在已经慢慢对ES6的原生Promise作了兼容,虽然ES6现在还没有大规模投入使用过程中。

在其中最为出名的则是bluebird和Q库,我使用的是bluebird,先贴一段bluebird的使用代码感受感受

bluebird

//CAST
//MrFileOne.txt
//MrFileTow.txt
//MrFileThree.txt //关系嵌套任务
var Promise = require("bluebird"),
readFileAsync = Promise.promisify(require("fs").readFile); readFileAsync("MrFileOne.txt","utf8")
.then(function(data){
//if the data contains the path of MrFileTow
var path = ..... //do something with data
return readFileAsync(path,"utf8");
})
.then(function(data){
//if the data contains the path of MrFileThree
var path = ..... //do something with data
return readFileAsync(path,"utf8");
})
.then(function(data){
//get the data of MrFileThree
//do something
})
.catch(function(err){
console.log(err);
}); //无关系汇总任务
Promise.all([
readFileAsync("MrFileOne.txt","utf8"),
readFileAsync("MrFileTwo.txt","utf8"),
readFileAsync("MrFileThree.txt","utf8")
])
.then(function(datas){
//do something with three data form our actors
})
.catch(function(err){
console.log(err);
});

有没有一下被这种写法所吸引,这就是Promise模块的魅力,它很优雅地将函数的回调写在了then里面,并为then返回一个新的Promise,以供下一次的then去回调本次返回的结果。

How

首先使用了方法:

readFileAsync = Promise.promisify(rquire("fs").readFile);

这个方法则是为复制了readFile方法并为其增添了Promise机制,而Promise机制是什么呢?那就是为其添加Promise方法和属性后,让整个方法的返回值为一个Promise对象,我们可以通过Promise来调用then方法,来去对这个Promise方法的回调进行处理。在Promise中都默认的将第一个参数err放在了后面的catch中,使得我们再也不用写那么多的if(err)了。我们可以直接通过在then方法中通过函数参数来获取这个Promise的异步数据,从而进行下一步的处理。

而在then方法后,其返回的也是一个Promise对象,我们可以在其后再次进行then来获取上一个then的数据并进行处理。当然,我们也可以人为地去决定这个then的返回参数,但是整个then方法返回的都是一个Promise对象。

readFileAsync("MrFileOne.txt","utf8")
.then(function(data){
if(.....){ //data isn't what we want
Promise.reject("It's not correct data!");
}else{
return data;
}
})
.then(function(){
console.log("yeah! we got data!");
})
.catch(function(err){
console.log(err);
})

在上面代码中,如果获取到的data并不是我们想要的,则我们可直接调用Promise.reject抛出一个ERROR,并直接交给catch来处理错误,所以在控制台我们能得到的是“It's not correct data!”,并不会得到“yeah! we got data!”,因为抛出错误后其之后的then方法并不会跟着执行。

More

当然我们也可以自定义多个catch来捕获不同的ERROR,对其作不同的处理,就像下面的一样

var customError = new Error(SOMENUMBER,SOMEDESCRIPTION)

readFileAsync("MrFileOne.txt","utf8")
.then(function(data){
switch(data){
case CASE1:
Promise.reject(customError);
case CASE2:
Promise.reject(new SyntaxError("noooooo!"));
}
})
.catch(customError,function(err){
//do with customError
})
.catch(SyntaxError,function(err){
//do with SyntaxError
})
.catch(function(err){
console.log(err);
})

而更多的使用方法,可以在bluebird on npm里学习得到,相信你看了之后会爱上Promise的。

Q

Q模块也是一个非常优秀的Promise,它的实现原理和bluebird都大同小异,都是基于Promise/A+标准来扩展的,所以使用上甚至都是差不了多少的,选择哪一个就看个人爱好了。

Promise编程思想


重点来啦,我们先来看一段普通的代码

var obj = (function(){
var variable; return {
get: function(){
return variable;
},
set: function(v){
variable = v;
}
}
})(); exports.get = obj.get;
exports.set = obj.set;

这个代码实现的是创建了一个闭包来储存变量,那么我在外部调用这个模块时,则可以去操作这个值,即实现了一个Scope变量,并把它封装了起来。

矛盾

根据我们以前的思想,这段代码看起来很正常,但是这时侯我要加一判断进去,即在get方法调用时,如果varibaleundefined,那么我则去做一个读文件的操作,从文件中将它读出来,并反回,你会怎么实现呢?

你会发现,通过以往的思维,你是无法做到这一方法的,那么使用异步思维去想想呢,好像有点门头:

get: function(callback){
if(varibale){
callback(varibale);
}else{
fs.readFile("SomeFile","utf8",function(err,data){
if(err){
//do with err
return;
} callback(data);
})
}
}

这样……嗯咳咳,看起来似乎好像也许解决的还可以,但是你自己也会觉得,这其实糟透了,我们将原本的简单get函数更改得这么复杂。那么问题来了,谁会在使用的时候会想到这个get方法其实是一个回调的方法呢?你平时使用get时你会考虑说是这个里面有可以是回调吗?我们都是直接get()来获取它的返回值。

这就是我们自己给自己造成的矛盾和麻烦,这也是我以前曾经遇到的。

突破

那么在模块化的node里,我们怎么去实现这些不必要的麻烦呢?那就是用Promise思想去编写自己的代码,我们先试着用上面说到的bluebird来加工一下这段代码:

var Promise = require("bluebird"),
fs = require("fs"); var obj = (function(){
var variable; return {
get: function(){
if(variable){
return Promise.resolve(variable);
} return Promise.promisify(fs.readFile)("SomeFile","utf8");
},
set: function(v){
return Promise.resolve(variable = v);
}
}
}); exports.get = obj.get;
exports.set = obj.set;

就是这么漂亮,使用Promise.resolve方法则是将变量转化为一个Promise对象,则是我们在外部对这个模块进行使用时,则要求我们使用Promise的思想去应用模块抛出的接口,比如:

var module = require("thisModule.js");

module.get()
.then(function(data){
console.log(data);
module.set("new String");
return module.get;
})
.then(function(data){
console.log(data);
});

当我们使用Promise思想去面对每一个接口的时候,我们可以完全不用考虑这个模块的代码是怎么写的,这个方法该怎么用才是对的,到底是回调还是赋值。我们可以很直接的在其模块方法后then来解决一切问题!不用关心前面的工作到底做了什么,怎么做的,到底是异步还是同步,只要我们将整个工作流程都使用Promise来做的话,那会轻松很多,而且代码的可读性会变得更好!

尼玛!简直是神器啊!

使用Promise编程思想去和node玩耍,你会相信真爱就在眼前。同时我也相信在前端模块化加速的今天,Promise编程思想必定会渗透至前端的更多角落。

Finish.

nodejs与Promise的思想碰撞的更多相关文章

  1. Nodejs Q promise设计思路

    Nodejs Q promise库 前言 Q库为nodejs提供了一个基于promise的编程方式,从此避免了一层又一层的callback调用.不过Q的灵活性也给我造成了很大困扰,我可以用promis ...

  2. nodejs使用promise实现sleep

    个人博客 地址:http://www.wenhaofan.com/article/20181120180225 let sleep = function (delay) { return new Pr ...

  3. [JS] ECMAScript 6 - Async : compare with c#

    一段引言: Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大. 它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对 ...

  4. 关于NodeJS的思考

    对于NodeJS来说传统程序员比较陌生,初看以为是什么前端框架,其实并不是前端框架.传统的Javascript只能跑在浏览器中,但是一位叫Ryan Dahl的开发者灵感一来,为什么Javascript ...

  5. q.js实现nodejs顺序调用

    nodejs的异步调用有时候是最让人头疼的,如何能是一些代码顺序的执行呢,这里和大家分享nodejs的promise 什么是promise promise一个标准,它描述了异步调用的返回结果,包括正确 ...

  6. 聊一聊promise的前世今生

    promise的概念已经出现很久了,浏览器.nodejs都已经全部实现promise了.现在来聊,是不是有点过时了? 确实,如果不扯淡,这篇随笔根本不会有太多内容.所以,我就尽可能的,多扯一扯,聊一聊 ...

  7. angular $q promise详解

    前言 通过本文,你大概能清楚angular promise是个啥,$q又是个啥,以及怎么用它.这里咱们先灌输下promise的思想. 下面写的全是废话,一些看着高逼格其实没什么大作用的概念,想知道$q ...

  8. Node.js最新技术栈之Promise篇

    前言 大家好,我是桑世龙,github和cnodejs上的i5ting,目前在天津创业,公司目前使用技术主要是nodejs,算所谓的MEAN(mongodb + express + angular + ...

  9. JS - Promise使用详解--摘抄笔记

    第一部分: JS - Promise使用详解1(基本概念.使用优点) 一.promises相关概念 promises 的概念是由 CommonJS 小组的成员在 Promises/A 规范中提出来的. ...

随机推荐

  1. sqoop 常用命令整理(一)

    这些内容是从sqoop的官网整理出来的,是1.4.3版本的Document,如果有错误,希望大家指正. 1.使用sqoop导入数据 sqoop import --connect jdbc:mysql: ...

  2. centos7安装rabbitmq3.7

    centos7安装rabbitmq3.7安装erlang # vim /etc/yum.repos.d/rabbitmq-erlang.repo [rabbitmq-erlang] name=rabb ...

  3. 【javascript】设为首页——setHome

    原生 js 编写,兼容 ie,火狐和谷歌. 函数如下: function setHome(obj,url){ try{ obj.style.behavior = 'url(#default#homep ...

  4. 七牛图片存储api

    https://developer.qiniu.com/kodo 属于对象存储 https://www.qiniu.com/prices 如何获取存储文件的外链接 https://developer. ...

  5. [impala] impala 简介

    [简介] Impala是Cloudera公司主导开发的新型查询系统,它提供SQL语义,能查询存储在Hadoop的HDFS和HBase中的PB级大数据.已有的Hive系统虽然也提供了SQL语义,但由于H ...

  6. 【转】]监听SMS消息/编程实现短信拦截

    当设备接收到一条新的SMS消息时,就会广播一个包含了android.provider.Telephony.SMS_RECEIVED动作的Intent.注意,这个动作是一个字符串值,SDK 1.0不再包 ...

  7. 基于php,点亮代码生成技能树

    <?php $table = ($_GET['action']); /*生成后端*/ $fileName="core/Motherboard.php"; $handle=fo ...

  8. Pandas 的轴向连接 concat

    在pandas里面,另一种数据何必运算也被称为连接(concatenation).绑定(binding)或堆叠(stacking). Numpy的轴向连接, concatenation Numpy有一 ...

  9. 锐捷 ac ap 连接 记录

    需要用到锐捷的ac管理2台ap.记录一下. 参考文档 锐捷WLAN无线产品一本通(V6.0): http://www.ruijie.com.cn/fw/wd/58033 1.确认AC无线交换机和AP是 ...

  10. 第三百二十五节,web爬虫,scrapy模块标签选择器下载图片,以及正则匹配标签

    第三百二十五节,web爬虫,scrapy模块标签选择器下载图片,以及正则匹配标签 标签选择器对象 HtmlXPathSelector()创建标签选择器对象,参数接收response回调的html对象需 ...