Node.js 异步编程

来源:互联网 发布:淘宝怎么开直通车 编辑:程序博客网 时间:2024/05/13 00:08

1. Node.js的编程方式

首先看一下JavaScript的setTimeout函数。该函数的参数包含一个函数调用和等待时间,参数函数会在等待时间到达后执行:

setTimeout(function () {    console.log('2s过了,到我了!');}, 2000);console.log('猜猜我是不是第一个被打印。');

运行上面代码,可以看到输出结果:

猜猜我是不是第一个被打印。2s过了,到我了!

现在看一下,很多时候在调用函数时,都需要等待外部资源(数据库服务器、网络请求或者文件系统的读写操作等),这些情况都是非常类似的。接下来以文件操作为例写一段程序:

如果用PHP编写的话,可能会是这样:

$file = fopen('info.txt', 'r');//等文件打开$content = fread($file, 1000);//等内容读完//然后使用读取到的内容

分析这段代码后,会发现大部分时间它什么都没做,它大部分时间都是在等电脑的文件系统执行。绝大部分基于IO的应用–那些需要频繁地连接数据库、和外部服务器通信或者读写文件的应用–都将花费大量时间等待处理结果。
服务器同时处理多个请求的方法就是并行这些脚本。现在的电脑操作系统能够很好地支持多任务处理,所以可以很容易地在进程阻塞时切换任务,以便让其他进程访问CPU。当然,还可以使用多线程代替多进程。但是每一个进程或者线程会消耗大量的系统资源。
那么有什么办法可以最大化利用CPU的计算能力和可用内存减少资源浪费呢,我们来看看Node.js是怎么做的。

使用新的异步函数来重写前面的同步脚本。

var fs = require("fs");//这里使用了Node.js的模块fs.open(    'info.txt', 'r',    function (err, handle) {        var buf = new Buffer(1000);        fs.read(            handle, buf, 0, 1000, null,            function (err, length) {                console.log(buf.toString("utf8", 0, length));                fs.close(handle, function() {                    console.log("关闭!");                });            }        );    });

异步函数的工作方式:
- 检查和验证参数
- 通知Node.js核心去排队调用相应的函数,并在返回结果的时候调用回调函数
- 返回给调用者
(Node.js使用了事件队列,如果有挂起的事件等待响应,程序是不会退出的)

2. 回调函数和错误处理(维护好本体)

现在写一个类来处理一些普通的文件操作:

function FileObject () {}FileObject.prototype.fileName = null;FileObject.prototype.setFileName = function (name) {    this.fileName = name;}FileObject.prototype.fileExists = function (callback) {    console.log("将要打开文件:"+this.fileName);    fs.open(this.fileName, 'r', function (err, handle) {        if (err) {            console.log("发生错误!"+this.fileName);            callback(err);            return;        }        fs.close(handle, function () {            console.log("文件已关闭!");        });        callback(null, this.fileName);    });}var fo = new FileObject();fo.setFileName("info.txt");fo.fileExists(function (err, result) {    if (err) {        console.log("发生错误:"+err);        return;    }    console.log('文件存在!' + result);});

执行以上代码,我们会看到结果:

将要打开文件:info.txt文件存在! undefined文件已关闭!

对,文件是存在的可是为什么打印出的文件名是undefined呢?思考下面这段代码:

FileObject.prototype.fileExists = function (callback) {    console.log("将要打开文件:"+this.fileName);    fs.open(this.fileName, 'r', function (err, handle) {        if (err) {            console.log("发生错误!"+this.fileName);            callback(err);            return;        }        fs.close(handle, function () {            console.log("文件已关闭!");        });        callback(null, this.fileName);    });}

其实像fs.open这样的函数,它会先初始化自己,然后调用底层的操作系统函数(在这里也就是打开文件),并把回调函数插入到事件队列中去。执行完毕之后会立即返回给FileObject.fileExists函数,然后退出。当fs.open函数完成后,Node会调用回调函数,但是这个回调函数已经不再有FileObject类的继承关系了,所以回调函数得到是新的this指针(在这里也就是undefined)。解决办法:因为闭包的缘故,回调函数是保存着fs.open的变量作用域的,我们可以在fs.open函数内部将原先的this指针赋值给一个新的变量,那么this指针就保存下来了。代码如下:

FileObject.prototype.fileExists = function (callback) {    var self = this;//改动1    console.log("将要打开文件:"+this.fileName);    fs.open(this.fileName, 'r', function (err, handle) {        if (err) {            console.log("发生错误!"+self.fileName);//改动2            callback(err);            return;        }        fs.close(handle, function () {            console.log("文件已关闭!");        });        callback(null, self.fileName);//改动3    });}

这时候运行结果就是正常的了:

将要打开文件:info.txt文件存在!info.txt文件已关闭!

请看一下以下代码:

function FileObject () {    this.fileName = null;    this.fileExists = function (callback) {        var self = this;//将当前对象指针赋予self        console.log("将要打开文件:"+this.fileName);        //文件一旦成功打开,this指针就不再指向这个对象了,它就没了,之后的回调函数就没法使用this了        fs.open(this.fileName, 'r', function (err, handle) {            if (err) {                console.log("发生错误!"+self.fileName);                callback(err);                return;            }            fs.close(handle, function() {                console.log("关闭!");            });            console.log("等0.1s!");//可以看到文件系统的清理关闭都是放最后执行的,首先执行close后可以立即执行的代码            //由于设置了1秒的延迟,该部分不会立即执行,所以close函数就执行了。            setTimeout(function () {                callback(null, self.fileName);            },1000);        });    }}var fo = new FileObject();fo.fileName = 'info.txt';fo.fileExists(function (err, result) {    if (err) {        console.log("发生错误:"+err);        return;    }    console.log("文件存在:"+result);});

3. 学会放弃控制权

现在我们需要一个函数计算两个数组的相等元素有哪些,并生成到新的数组中去,我们可能会这样做:

function computeIntersection(arr1, arr2, callback) {    var result = [];    for (var i = 0; i < arr1.length; i++) {        for (var j = 0; j < arr2.length; j++) {            if (arr1[i] == arr2[j]) {                result.push(arr1[i]);            }        }    }    callback(null, result);}var a1 = [3476, 2457, 7547, 34523, 3, 6, 7, 2, 77, 8, 2345, 7623457, 2347, 23572457, 234869, 237, 24572457524];var a2 = [3476, 75347547, 24576425, 22344319, 323, 34574, 2357, 7, 34652346, 1345324, 572346, 237, 234, 24352345,          2345, 8553577, 25577257, 3, 234869, 33253, 24572457524, 87378326632];var label = "运行时间:";console.time(label);//开始比较computeIntersection(a1, a2, function (err, result) {    if (err) {        console.log("错误!");        return;    }    console.log(result);})console.timeEnd(label);

看一下输出结果:

[ 3476, 3, 7, 2345, 234869, 237, 24572457524 ]运行时间:: 11.501ms

运行了11.501ms,只能说太慢了!改进一下:

function computeIntersection (arr1, arr2, callback) {    var biggerArray = arr1.length > arr2.length ? arr1 : arr2;    var smallerArray = biggerArray == arr1 ? arr2 : arr1;    var biglen = biggerArray.length;    var smalllen = smallerArray.length;    var sidx = 0;    var size = 10;    var result = [];    //一次比较10个元素    function subComputeIntersection () {        for (var i = sidx; i < (sidx + size) && i < biglen; i++) {            for (var j = 0; j < smalllen; j++) {                if (biggerArray[i] == smallerArray[j]) {                    result.push(smallerArray[j]);                    break;                }            }        }        if (i >= biglen) {            callback(null, result);//比较完了,可以返回了        } else {            sidx += size;            process.nextTick(subComputeIntersection);//重点在这!!!        }    }    subComputeIntersection();}var a1 = [3476, 2457, 7547, 34523, 3, 6, 7, 2, 77, 8, 2345, 7623457, 2347, 23572457, 234869, 237, 24572457524];var a2 = [3476, 75347547, 24576425, 22344319, 323, 34574, 2357, 7, 34652346, 1345324, 572346, 237, 234, 24352345,          2345, 8553577, 25577257, 3, 234869, 33253, 24572457524, 87378326632];var label = "运行时间:";console.time(label);computeIntersection(a1, a2, function (err, result) {    if (err) {        console.log("错误!");        return;    }    console.log(result);})console.timeEnd(label);

赶紧运行一下:

运行时间:: 1.152ms[ 3476, 7, 237, 2345, 3, 234869, 24572457524 ]

嗯,将近10倍,没毛病!分析一下:

在这里,我们使用了全局对象process中的nextTick方法。可以这样理解它–我放弃控制权,您在空闲的时候执行下我给您的函数好了。相较于setTimeout函数,这种方式会显著提高执行速度的。在这个程序中我简单地将较大的数组分割成10个元素一组的数据块,分别与较小数组比较,然后调用process.nextTick函数,从而允许Node处理其他事情或请求。只有当该任务的队列前面没有事情时,才继续执行任务,注意回调函数是要传给process.nextTick的,这样才能保证当前作用域能作为闭包保存下来。

0 0
原创粉丝点击