Node.js编程之异步
异步操作
Node采用V8引擎处理JavaScript脚本,最大特点就是单线程运行,一次只能运行一个任务。这导致Node大量采用异步操作(asynchronous opertion),即任务不是马上执行,而是插在任务队列的尾部,等到前面的任务运行完后再执行。
由于这种特性,某一个任务的后续操作,往往采用回调函数(callback)的形式进行定义。
var isTrue = function(value, callback) {
if (value === true) {
callback(null, "Value was true.");
}
else {
callback(new Error("Value is not true!"));
}
}
上面代码就把进一步的处理,交给回调函数callback。
如果没有发生错误,回调函数的第一个参数就传入null。这种写法有一个很大的好处,就是说只要判断回调函数的第一个参数,就知道有没有出错,如果不是null,就肯定出错了。另外,这样还可以层层传递错误。
Node约定,如果某个函数需要回调函数作为参数,则回调函数是最后一个参数。另外,回调函数本身的第一个参数,约定为上一步传入的错误对象。
var callback = function (error, value) {
if (error) {
return console.log(error);
}
console.log(value);
}
异步开发的难题
在创建异步程序时,你必须密切关注程序的执行流程,并盯牢程序状态:事件轮训的条件、程序变量以及其他随着程序逻辑执行而发生变化的资源。如果不小心,程序的变量也可能会出现意想不到的变化。下面这段代码是一段因为执行顺序而导致混乱的异步代码。
如果例子中的代码能够同步执行,可以肯定输出的应该是"The color is blue",可这个例子是异步的,在console.log执行前color的值还在变化,所以输出是"The color is green".
function asyncFunction(callback) {
setTimeout(callback, 200)
} var color = 'blue' asyncFunction(function(){
console.log('The color is ' + color) // The color is green.(这个最后执行(200ms之后))
}) color = 'green'
用JavaScript闭包可以"冻结"color的值,在如下代码中,对asyncFunction的调用被封装到了一个以color为参数的匿名函数里,这样就可以马上执行这个匿名函数,把当前的color的值传给它。而color变成了匿名函数的参数,也就是这个匿名函数内部的本地变量,当匿名函数外面的color值发生变化时,本地版的color不会受影响。
function asyncFunction(callback) {
setTimeout(callback, 200)
} var color = 'blue' (function(color) {
asyncFunction(function(){
console.log('The color is ' + color) // The color is blue.
})
})(color); color = 'green
在Node开发中需要用到很多JavaScript编程技巧,这只是其中之一。
现在我们知道怎么用闭包控制程序的状态了,接下来我们看看怎么让异步逻辑顺序执行。
异步流程的顺序化
让一组异步任务顺序执行的概念被Node社区称为流程控制。这种控制分为两类:串行和并行,
什么时候使用串行流程控制
可以使用回调让几个异步任务按顺序执行,但如果任务很多,必须组织一下,否则会陷入回调地狱。
下面这段代码就是用回调让任务顺序执行的。
setTimeout(function(){
console.log('I execute first.')
setTimeout(function(){
console.log('I execute next.')
setTimeout(function(){
console.log('I execute last.')
}, 100)
}, 500)
}, 1000)
此外,也可以用Promise这样的流程控制工具来执行这些代码
promise.then(function(result){
// dosomething
return result;
}).then(function(result) {
// dosomething
return promise1;
}).then(function(result) {
// dosomething
}).catch(function(ex) {
console.log(ex);
}).finally(function(){
console.log("final");
});
接着我们通过例子,自己来实现串行化流程控制和并行化流程控制
实现串行化流程控制
为了用串行化流程控制让几个异步任务按顺序执行,需要先把这些任务按预期的执行顺序放到一个数组中。如下图所示:
下面是一个串行化流程控制的demo,实现了从随机选择的RSS预定源中获取一篇文章的标题和URL,源文件
// 在一个简单的程序中实现串行化流程控制
var fs = require('fs')
var request = require('request') // 用它获取RSS数据
var htmlparser = require('htmlparser') // 把原始的RSS数据转换成JavaScript结构
var configFilename = './rss_feeds.txt' function checkForRSSFile() { // 任务1:确保包含RSS预定源URL列表的文件存在
fs.exists(configFilename, function(exists) {
if (!exists) {
return next(new Error('Missing RSS file: ' + configFilename)) // 只要有错误就尽早返回
}
next(null, configFilename)
})
} function readRSSFile (configFilename) { // 任务2:读取并解析包含预定源URL的文件
fs.readFile(configFilename, function(err, feedList) {
if (err) {
return next(err)
} feedList = feedList // 讲预定源URL列表转换成字符串,然后分隔成一个数组
.toString()
.replace(/^\s+|\s+$/g, '')
.split("\n");
var random = Math.floor(Math.random()*feedList.length) // 从预定源URL数组中随机选择一个预定源URL
next(null, feedList[random])
})
}
// console.log('进入') function downloadRSSFeed(feedUrl) { // 任务3:向选定的预定源发送HTTP请求以获取数据
request({uri: feedUrl}, function(err, res, body) {
if (err) {
return next(err)
}
if (res.statusCode != 200) {
return next(new Error('Abnormal response status code'))
} next(null, body)
})
} function parseRSSFeed(rss) { // 任务4:将预定源数据解析到一个条目数组中
var handler = new htmlparser.RssHandler()
var parser = new htmlparser.Parser(handler)
parser.parseComplete(rss)
if (!handler.dom.items.length) {
return next(new Error('No RSS items found'))
}
console.log(handler.dom.items)
var item = handler.dom.items.shift()
console.log(item.title)
console.log(item.link)
}
var tasks = [ checkForRSSFile, // 把所有要做的任务按执行顺序添加到一个数组中
readRSSFile,
downloadRSSFeed,
parseRSSFeed ] function next(err, result) {
if (err) {
throw err
} var currentTask = tasks.shift() // 从任务数组中取出下个任务 if (currentTask) {
currentTask(result) // 执行当前任务
}
} next() // 开始任务的串行化执行
如本例所示,串行化流程控制本质上是在需要时让回调进场,而不是简单地把它们嵌套起来
实现并行化流程控制
为了让异步任务并行执行,仍然是要把任务放到数组中,但任务的存放顺序无关紧要。每个任务都应该调用处理器函数增加已完成任务的计数值。当所有任务都完成后,处理器函数应该执行后续的逻辑。
来看一个并行化流程控制的小demo,该demo实现了在控制台中统计打印出所有单词分别出现的总数。源文件
// 在一个简单的程序中实现并行流程控制
var fs = require('fs')
var completedTasks = 0
var tasks = []
var wordCounts = {}
var filesDir = './text' function checkIfComplete() { // 当所有任务全部完成后,列出文件中用到的每个单词以及用了多少次
completedTasks++
// console.log(completedTasks)
console.log(tasks.length)
if (completedTasks == tasks.length) {
for(var index in wordCounts) {
console.log(index + ': ' + wordCounts[index])
}
}
} function countWordsInText(text) {
var words = text
.toString()
.toLowerCase()
.split(/\W+/)
.sort()
for (var index in words) { // 对文本中出现的单词计数
var word = words[index]
if (word) {
wordCounts[word] = (wordCounts[word]) ? wordCounts[word] + 1 : 1
}
}
} fs.readdir(filesDir, function(err, files) { // 得出text目录中的文件列表
if (err) {
throw err
}
for(var index in files) {
var task = (function(file) { // 定义处理每个文件的任务,每个任务中都会调用一个异步读取文件的函数并对文件中使用的单词计数
return function() {
fs.readFile(file, function(err, text) { // 这里注意fs.readFile()是一个异步进程,countWordsInText(),checkIfComplete()方法会在tasks.push()方法后面进行
if (err) {
throw err
}
countWordsInText(text)
checkIfComplete()
})
}
})(filesDir + '/' + files[index])
tasks.push(task)
}
for(var task in tasks) {
tasks[task]()
}
})
如上两个demos阐述了串行和并行化流程控制的底层机制。
总结
可以用回调、事件发射器和流程控制管理异步逻辑。回调适用于一次性异步逻辑;事件发射器对组织异步逻辑很有帮助,因为它们可以把异步逻辑跟一个概念实体关联起来,可以通过监听器轻松管理;流程控制可以管理异步任务的执行顺序,可以让它们一个接一个执行,也可以同步执行。你可以自己实现流程管理,但社区附加模块可以帮你解决这个麻烦。选择哪个流程控制附加模块很大程度取决于个人喜好以及项目或设计的需求。
Node.js编程之异步的更多相关文章
- 17.Node.js 回调函数--异步编程
转自:http://www.runoob.com/nodejs/nodejs-tutorial.html Node.js 异步编程的直接体现就是回调. 异步编程依托于回调来实现,但不能说使用了回调后程 ...
- Node.js入门:异步IO
异步IO 在操作系统中,程序运行的空间分为内核空间和用户空间.我们常常提起的异步I/O,其实质是用户空间中的程序不用依赖内核空间中的I/O操作实际完成,即可进行后续任务. 同步IO的并行模式 ...
- Node.js中的异步I/O是如何进行的?
Node.js的异步I/O通过事件循环的方式实现.其中异步I/O又分磁盘I/O和网络I/O.在磁盘I/O的调用中,当发起异步调用后,会将异步操作送进libuv提供的队列中,然后返回.当磁盘I/O执行完 ...
- Node.js编程规范
摘自:https://github.com/dead-horse/node-style-guide https://github.com/felixge/node-style-guide 2空格缩进 ...
- 【第三周读书笔记】浅谈node.js中的异步回调和用js-xlsx操作Excel表格
在初步学习了node.js之后,我发现他的时序问题我一直都很模糊不清,所以我专门学习了一下这一块. 首先我们来形象地理解一下进程和线程: 进程:CPU执行任务的模块.线程:模块中的最小单元. 例如:c ...
- node.js整理 06异步编程
回调 异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了 function heavyCompute(n, callback) { var count = 0, i, j; for (i = ...
- 深入理解node.js异步编程:基础篇
###[本文是基础内容,大神请绕道,才疏学浅,难免纰漏,请各位轻喷] ##1. 概述 目前开源社区最火热的技术当属Node.js莫属了,作为使用Javascript为主要开发语言的服务器端编程技术和平 ...
- Node.js之Promise维护(同步)多个回调(异步)状态
金天:学习一个新东西,就要持有拥抱的心态,如果固守在自己先前的概念体系,就会有举步维艰的感觉..NET程序员初用node.js最需要适应的就是异步开发, 全是异步,常规逻辑下遍历列表都是异步,如何保证 ...
- node.js 异步式I/O 与事件驱动
Node.js 最大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式.这种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一 ...
随机推荐
- [转]ios 数据的传递
情景1: A-->B 需要把数据传递到B里 代码: // 跳转 -- 执行login2contacts这个segue [self performSegueWithIdentifier:@&qu ...
- linux 下安装eclipse和pydev插件用来开发python程序
1.网上的教程要求必须要安装java虚拟机,但是我觉得不用,但是为了不出意外就装吧. (1)去官网下载安装包www.sun.com (2)安装包是.tar.gz的可以用 tar -xzvf +压缩包路 ...
- 归纳篇(一)CSS的position定位和float浮动
近期会更新一系列博客,对基础知识再度做个巩固和梳理. 一.position定位 (一):position的属性 1.absolute:生成绝对定位的元素,相对于最近一级定位不是static的父元素来进 ...
- 从CMOS到触发器(二)
PS:可以转载,转载请标明出处:http://www.cnblogs.com/IClearner/ 前面说了CMOS器件,现在就接着来聊聊锁存器跟触发器吧,下面是这次博文要介绍的主要内容: ·双稳态器 ...
- Android.mk模板(持续更新中)
此文列出Android.mk的常用模板(部分内容源于多篇他人博客,这里不具体指出),如有错漏,还请在评论中指出,后期持续更新 #链接第三方动态库,在和部分android源码的编译中验证不过 LOC ...
- Kindle电子阅读器收不到个人文档推送解决方案
最近我的 kindle 固件版本更新到 5.8.7.0.1 ,发现增加了生字注音功能,瞬间变成小学生阅读神器有木有,不过,这个功能可以隐藏.显示,看着碍眼隐藏即可,还可以减少和增加生字注音.感觉对于经 ...
- MES项目参观交流会
2016年11月10日,普实软件组织了河南蔚林.江苏正恺.吴通控股.上海锐拓等单位的26位企业家代表,走进科兴电器,开展企业家现场交流活动.企业家们参观了科兴花园式数字化工厂.感受了大数据中心的强大功 ...
- Java工程师:四个月小白变大咖,你能做到吗?
你眼中的Java工程师是什么样子? 技术大牛?闷骚男?IT民工?没有女朋友?全是汉子?很邋遢?贼眉鼠眼? 今天,中软国际卓越工程师,Java精英班正式开课啦.你想看看他们都是一群怎样的人吗? 今天的武 ...
- Redis 安装与简单使用
安装 Redis 一般系统都会有软件管理工具,但是通常版本都不会太新,况且 Redis 的安装很简单,因此下面使用源码的安装方式. 下载源码 wget http://download.redis.io ...
- 初次接触java中的递归算法
一道关于兔子繁衍的编程题: 有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 自己考虑了挺久,思路出现了问题,甚至连 ...