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的,这样才能保证当前作用域能作为闭包保存下来。
- node.js异步编程
- Node.js 异步编程
- Node.js 异步编程
- Node.js[1] 异步编程
- Node.js 异步编程之 Callback
- node.js 异步编程解决方法 了解一
- Node.JS的异步编程风格
- Node.js 异步编程之 Callback介绍
- Node.js中同步函数异步编程
- Node.js异步编程,promise,fibers
- Node.js 异步编程基础理解
- 深入理解node.js异步编程:基础篇
- Node.js 异步式 I/O 与事件式编程
- 理解Node.js的事件驱动和异步编程
- node.js异步IO和事件式编程
- Node.js 异步式 I/O 与事件式编程
- node.js异步式IO与事件式编程
- Node异步编程体验
- 动态规划-----最长公共连续子串
- 3.系统延迟及定时机制
- IntelliJ Idea构建Spark(scala)项目
- 浅析java设计模式
- 使用Volley加载网络图片
- Node.js 异步编程
- ThinkPHP3.2.1简介
- Hyperledger fabric 学习笔记: fabric v1.0 代码结构
- 关于iOS swift3.0 UICollectionView封装引导页和轮播图
- ortp 队列介绍
- PHP开发者的Linux学习之路
- Leetcode402——Remove K Digits
- atoi函数的实现
- 5.使用逻辑卷管理器管理灵活存储 lvm