nodejs与Promise的思想碰撞

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

众所周知,javascript做为一个单线程语言,全部工做都是阻塞的,有好多人不理解为何说是javascript是阻塞的,怎么能够作到异步机制呢?前端

举一个栗子

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

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

现实


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

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为第一个参数,使得上层全部异步方法都为这种模式)promise

进化


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

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.

相关文章
相关标签/搜索