node.js怎么用nodejs回调函数返回值设置带宽

专门针对初学者的Node.js教程
发表于 14:09|
来源Modulus Blog|
作者Modulus Blog
摘要:这是一篇专门针对Node.js初学者的教程。该教程将通过具体实例,教你一步步开始Node.js之旅。
Node.js的教程并不缺乏,但有大部分教程针对已有Node.js基础的开发者。“我已下载了Node.js,可我如何开始呢?”以下教程是专门针对Node.js初学者,文中将通过实例分享,一步步教你如何开始Node.js之旅。 &什么是Node.js?很多初学者并没有真正地理解Node.js到底是什么。网站中的描述也没有多大帮助。首先要清楚Node不是一个Web服务器,这十分重要。它本身并不能做任何事情。它无法像Apache那样工作。如果你希望它成为一个HTTP服务器,你必须借助它内置库自己编写。Node.js只是计算机上执行代码的另一种方式,它是一个简单的JavaScript Runtime.安装Node.jsNode.js的安装十分容易。只需满足你需要的安装程序即可。已安装好Node.js,下一步做什么?安装结束后,你可以输入一个新命令“node”。使用该“node”命令有两种不同的方法。第一种不带任何参数,将打开一个交互式Shell“&”(REPL: read-eval-print-loop),你可以在这里执行JavaScript代码。$ node
& console.log('Hello World');
Hello World
undefined上面案例中,我在Shell中键入了“console.log('Hello World')”,并敲回车。Node便开始执行该代码,并显示刚才记录的信息,同时打印出“undefined”。这是因为每条命令都会返回一个值,而console.log没有任何返回,故输出“undefined”。Node命令的另一种用法是执行一个JavaScript文件。这是我们平时最常用的方法。hello.js&b&console.log('Hello World');&/b&&b&$ node hello.js
Hello World&/b&该案例中,我将“console.log('Hello World');”命令存入一个文件中,并将该文件作为node命令的参数。Node运行文件中JavaScript代码,并输出“Hello World”。案例一:文件的输入与输出Node.js包含一组(模块),可以帮助我们做很多事。第一个案例中,我将打开一个Log文件,并对它进行解析。example_log.txt&b&T13:50:33.166Z A 2
T13:51:33.166Z B 1
T13:52:33.166Z C 6
T13:53:33.166Z B 8
T13:54:33.166Z B 5&/b&该Log数据什么意思并不重要,基本可以确定每条信息都包含一条数据、一个字母和一个值。我希望将每个字母后面的值进行累加。我们要做的第一件事是读出文件的内容。my_parser.js&b&// Load the fs (filesystem) module
var fs = require('fs');
// Read the contents of the file into memory.
fs.readFile('example_log.txt', function (err, logData) {
// If an error occurred, throwing it will
// display the exception and end our app.
// logData is a Buffer, convert to string.
var text = logData.toString();
});&/b&通过内置的,我们可以很容易进行文件的输入/输出操作。fs模块有一个方法,该方法以文件路径、回调函数为参数。该回调函数在完成文件读取后调用。文件数据读取后存储在类型中,为基本的字节数组。我们可以通过()方法将它转化为字符串。现在我们对它进行解析。my_parser.js&b&// Load the fs (filesystem) module.
var fs = require('fs');
// Read the contents of the file into memory.
fs.readFile('example_log.txt', function (err, logData) {
// If an error occurred, throwing it will
// display the exception and kill our app.
// logData is a Buffer, convert to string.
var text = logData.toString();
var results = {};
// Break up the file into lines.
var lines = text.split('\n');
lines.forEach(function(line) {
var parts = line.split(' ');
var letter = parts[1];
var count = parseInt(parts[2]);
if(!results[letter]) {
results[letter] = 0;
results[letter] += parseInt(count);
console.log(results);
// { A: 2, B: 14, C: 6 }
});&/b&现在,当你将该文件作为node命令的参数时,执行该命令将打印出如下结果,执行完毕后退出。$ node my_parser.js
{ A: 2, B: 14, C: 6 }我大部时候将Node.js作为脚本使用,正如上面所展示的那样。它更易于使用,是脚本程序有力的替代者。异步回调正如在上例中看到的那样,Node.js典型的模式是使用异步回调。基本上,你告诉Node.js要做的事,它执行完后便会调用你的函数(回调函数)。这是因为Node是单线程的。在你等待回调函数执行过程中,Node可继续执行其他事务,不必被阻塞直到该请求完毕。这对于Web服务器尤其重要。在现代Web应用访问数据库的过程中特别普遍。当你等待数据库返回结果的过程中,Node可以处理更多请求。与每次连接仅处理一个线程相比,它使你以很小的开销来处理成千上万个并行连接。案例二:HTTP服务器Node内建有一个模块,利用它可以很容易创建基本的。请看下面案例。my_web_server.js&b&var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(8080);
console.log('Server running on port 8080.');&/b&在上面,我说是的基本HTTP服务器。该例中所创建的并不是一个功能全面的HTTP服务器,它并不能处理任何HTML文件、图片。事实上,无论你请求什么,它都将返回“Hello World”。你运行该代码,并在浏览器中输入“http://localhost:8080”,你将看见该文本。$ node my_web_server.js现在你可能已经注意到一些不一样的东西。你的Node.js应用并没有退出。这是因为你创建了一个服务器,你的Node.js应用将继续运行,并响应请求,直到你关闭它。如果你希望它成为一个全功能的Web服务器,你必须检查所收到的请求,读取合适的文件,并返回所请求的内容。值得高兴的是,有人已经帮你做了这个艰难的工作。案例三:Express框架为一个框架,可使创建网站的过程十分简单。你首先需要安装它。除了node命令,你还需要访问“npm”命令。利用该工具,你可以访问社区所创建的庞大模块集。其中之一就是Express。$ cd /my/app/location
$ npm install express当你安装了一个模块,它将出现在应用程序所在目录的“node_modules”文件夹中。现在我们可以利用Express来创建一个基本的静态文件服务器。my_static_file_server.js&b&var express = require('express'),
app = express();
app.use(express.static(__dirname + '/public'));
app.listen(8080);&/b&$ node my_static_file_server.js现在你已创建了一个强大的静态文件服务器。你可以通过浏览器请求访问你放在public文件夹中任何文件,并进行展示,包括HTML、图片等任何东西。比如,把一个名为“my_image.png”的图片放在public文件夹中,你可以在浏览器中输入“http://localhost:8080/my_image.png”来访问该图片。当然,Express还有很多特性,你可以在以后的开发中继续探索。NPM上面我们已经接触到了,但我仍想强调一下在Node.js开发过程中该工具的重要性。它有成千上万个模块可帮我们解决遇到的大部分典型问题。在重新发明轮子之前,记得检查一下npm中是否有相应功能。&上一例中,我们手动安装了Express。如果你的程序包含很多“依赖”(Dependency),那再利用该方法安装它们就不合适了。为此npm提供了一个package.json文件。package.json&b&{
"name" : "MyStaticServer",
"version" : "0.0.1",
"dependencies" : {
"express" : "3.3.x"
}&/b&文件包含了应用程序的基本信息。其中“dependencies”部分描述了你想安装模块的名称和版本。该案例,接受Express 3.3的任何版本。你可以在该部分列出你想要的所有依赖。代替之前一个个安装每个依赖,现在我们可以运行一个命令,即可将它们全部安装完成。$ npm install运行该命令,npm将在当下文件夹中查找“package.json”文件。一旦找到,即可安装所列出的所有依赖。代码的组织在大部分应用程序中,你的代码往往被分割到几个文件中。现在让我们把最开始案例中的Log分析脚本分离出来。这样该程序将更易于测试与维护。parser.js&b&// Parser constructor.
var Parser = function() {
// Parses the specified text.
Parser.prototype.parse = function(text) {
var results = {};
// Break up the file into lines.
var lines = text.split('\n');
lines.forEach(function(line) {
var parts = line.split(' ');
var letter = parts[1];
var count = parseInt(parts[2]);
if(!results[letter]) {
results[letter] = 0;
results[letter] += parseInt(count);
// Export the Parser constructor from this module.
module.exports = P&/b&在此创建了一个新文件,来存放Log分析脚本。这仅仅是一种标准JavaScript,还有很多方法可用来封装该代码。我选择重新定义一个JavaScript对象,这样更容易进行单元测试。该程序中最重要的部分是“module.exports = P”这一行代码。它告诉Node从该文件中要输出的内容。在该例中,我输出了构造函数,用户可以用Parser对象来创建实例。你可以输出任何你想要的。现在我们看一下,如何导入该文件,来使用Parser对象。my_parser.js&b&// Require my new parser.js file.
var Parser = require('./parser');
// Load the fs (filesystem) module.
var fs = require('fs');
// Read the contents of the file into memory.
fs.readFile('example_log.txt', function (err, logData) {
// If an error occurred, throwing it will
// display the exception and kill our app.
// logData is a Buffer, convert to string.
var text = logData.toString();
// Create an instance of the Parser object.
var parser = new Parser();
// Call the parse function.
console.log(parser.parse(text));
// { A: 2, B: 14, C: 6 }
});&/b&如模块一样,文件被引入其中,你需要输入路径,而非名称。总结希望该教程可以帮助到你。Node.js是一个强大、灵活的技术,可以帮助解决各种各样的问题。它已经超出了我们的想像。(编译:陈秋歌 审校:夏梦竹)原文链接:
推荐阅读相关主题:
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章966,690 五月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
Node.js软肋之回调大坑
Node.js软肋之回调大坑
日. 估计阅读时间:
相关厂商内容
相关赞助商
ArchSummit深圳-8日,深圳&华侨城洲际酒店,
在JavaScript中,这个&后续逻辑体&就是我们常说的。这种作为参数的函数之所以被称为回调,是因为它一般在主程序中定义,由主程序交给库函数,并由它在需要时回来调用。而将回调函数作为参数的,一般是一个会占用较长时间的异步函数,要交给另一个线程执行,以便不影响主程序的后续操作。如下图所示:
在JavaScript代码中,后续传递风格就是在CPS函数的逻辑末端调用传入的回调函数,并把计算结果传给它。但在不需要执行处理时间较长的异步函数时,一般并不需要用这种风格。我们先来看个简单的例子,编程求解一个简单的5元方程:
x+y+z+u+v=16
x+y+z+u-v=10
x+y+z-u=11
对于x+y=a;x-y=b这种简单的二元方程我们都知道如何求解,这个5元方程的运算规律和这种二元方程也没什么区别,都是两式相加除以2求出前一部分,两式相减除以2求出后一部分。5元方程的前一部分就是4元方程的和值,依次类推。我们的程序写出来就是:
代码清单1. 普通解法-calnorm.js
var res = new Int16Array([16,10,11,8,2]),l= res.
var variables = [];
for(var i = 0;i &i++) {
if(i === l-1) {
variables[i] = res[i];
variables[i] = calculateTail(res[i],res[i+1]);
res[i+1] = calculateHead(res[i],res[i+1]);
function calculateTail(x,y) {
return (x-y)/2;
function calculateHead(x,y) {
return (x+y)/2;
方程式的结果放在了一个整型数组中,我们在循环中依次遍历数组中的头两个值res[i]和res[i+1],用calculateTail计算最后一个单值,比如第一和第二个等式中的v;用calculateHead计算等式的&前半部分&,比如第一和第二个等式中的x+y+z+u部分。并用该结果覆盖原来的差值等式,即用x+y+z+u的结果覆盖原来x+y+z+u-v的结果,以便计算下一个tail,直到最终求出所有未知数。
如果calculateTail和calculateHead是CPU密集型的计算,我们通常会把它放到子线程中执行,并在计算完成后用回调函数把结果传回来,以免阻塞主进程。关于CPU密集型计算的相关概念,可参考本系列的上一篇。比如我们可以把代码改成下面这样:
代码清单2. 回调解法-calcb.js
var res = new Int16Array([16,10,11,8,2]),l= res.
var variables = [];
(function calculate(i) {
if(i === l-1) {
variables[i] = res[i];
console.log(i + &:& + variables[i]);
process.exit();
calculateTail(res[i],res[i+1],function(tail) {
variables[i] =
calculateHead(res[i],res[i+1],function(head) {
res[i+1] =
console.log('-----------------'+i+'-----------------')
calculate(i+1);
function calculateTail(x,y,cb) {
setTimeout(function(){
var tail = (x-y)/2;
function calculateHead(x,y,cb) {
setTimeout(function(){
var head = (x+y)/2;
跟上一段代码相比,这段代码主要有两个变化。第一是calculateTail和calculateHead里增加了setTimeout,把它们伪装成CPU密集型任务;第二是弃用for循环,改用函数递归。因为calculateHead的计算结果会影响下一轮的calculateTail计算,所以calculateHead计算要阻塞后续计算。而for循环是无法阻塞的,会产生错误的结果。此外就是calculateTail和calculateHead都变成后续传递风格的函数了,通过回调返回最终计算结果。
这个例子比较简单,既不能充分体现回调在处理异步非阻塞操作时在性能上的优越性,坑的深度也不够恐怖。不过也可以说明&用后续传递风格实现几个异步函数的顺序执行是产生回调大坑的根本原因&。下面有一个更抽象的回调样例,看起来更有代表性:
module.exports = function (param, cb) {
asyncFun1(param, function (er, data) {
if (er) return cb(er);
asyncFun2(data,function (er,data) {
if (er) return cb(er);
asyncFun3(data, function (er, data) {
if (er) return cb(er);
像function(er,data)这种回调函数签名很常见,几乎所有的Node.js核心库及第三方库中的CPS函数都接收这样的函数参数,它的第一个参数是错误,其余参数是CPS函数要传递的结果。比如Node.js中负责文件处理的模块,我们再看一个实际工作中可能会遇到的例子。要找出一个目录中最大的文件,处理步骤应该是:
用获取目录中的文件列表;
循环遍历文件,获取文件的;
找出最大文件;
以最大文件的文件名为参数调用回调。
这些都是异步操作,但需要顺序执行,后续传递风格的代码应该是下面这样的:
代码清单3. 寻找给定目录中最大的文件
var fs = require('fs')
var path = require('path')
module.exports = function (dir, cb) {
fs.readdir(dir, function (er, files) { // [1]
if (er) return cb(er)
var counter = files.length
var errored = false
var stats = []
files.forEach(function (file, index) {
fs.stat(path.join(dir,file), function (er, stat) { // [2]
if (errored) return
errored = true
return cb(er)
stats[index] = stat // [3]
if (--counter == 0) { // [4]
var largest = stats
.filter(function (stat) { return stat.isFile() }) // [5]
.reduce(function (prev, next) { // [6]
if (prev.size & next.size) return prev
return next
cb(null, files[stats.indexOf(largest)]) // [7]
对这个模块的用户来说,只需要提供一个回调函数function(er,filename),用两个参数分别接收错误或文件名:
var findLargest = require('./findLargest')
findLargest('./path/to/dir', function (er, filename) {
if (er) return console.error(er)
console.log('largest file was:', filename)
介绍完CPS和回调,我们接下来看看如何平坑。
编写正确的并发程序归根结底是要让尽可能多的操作同步进行,但各操作的先后顺序仍能正确无误。服务端的代码一般逻辑比较复杂,步骤多,此时用嵌套实现异步函数的顺序执行会比较痛苦,所以应该尽量避免嵌套,或者降低嵌套代码的复杂性,少用匿名函数。这一般有几种途径:
最简单的是把匿名函数拿出来定义成单独的函数,然后或者像原来一样用嵌套方式调用,或者借助流程控制模块放在数组里逐一调用;
用Promis;
如果你的Node版本&=0.11.2,可以用generator。
我们先介绍最容易理解的流程控制模块。
流程控制模块
是一个轻量、可移植的函数式流程控制模块。经过最小化和压缩后只有837字节,可以运行在Node.js中,也可以用在各种浏览器中。它整合了和一些最实用的功能,并且API更简单。
nimble有两个流程控制函数,_.parallel和_.series。顾名思义,我们要用的是第二个,可以让一组函数串行执行的_.series。下面这个命令是用来安装Nimble的:
npm install nimble
如果用.series调度执行上面那个解方程的函数,代码应该是这样的:
var flow = require('nimble');
(function calculate(i) {
if(i === l-1) {
variables[i] = res[i];
process.exit();
flow.series([
function (callback) {
calculateTail(res[i],res[i+1],function(tail) {
variables[i] =
callback();
function (callback) {
calculateHead(res[i],res[i+1],function(head) {
res[i+1] =
callback();
function(callback){
calculate(i+1);
.series数组参数中的函数会挨个执行,只是我们的calculateTail和calculateHead都被包在了另一个函数中。尽管这个用流程控制实现的版本代码更多,但通常可读性和可维护性要强一些。接下来我们介绍Promise。
什么是呢?在的第一季第一集中,当琳达告诉安德伍德不能让他做国务卿后,他说:&所谓Promise,就是说它不会受不断变化的情况影响。&
Promise不仅去掉了嵌套,它连回调都去掉了。因为按照Promise的观点,回调一点也不符合函数式编程的精神。回调函数什么都不返回,没有返回值的函数,执行它仅仅是因为它的副作用。所以用回调函数编程天生就是指令式的,是以副作用为主的过程的执行顺序,而不是像函数那样把输入映射到输出,可以组装到一起。
最好的函数式编程是声明式的。在指令式编程中,我们编写指令序列来告诉机器如何做我们想做的事情。在函数式编程中,我们描述值之间的关系,告诉机器我们想计算什么,然后由机器(底层框架)自己产生指令序列完成计算。Promise把函数的结果变成了一个与时间无关的值,就像算式中的未知数一样,可以用它轻松描述值之间的逻辑计算关系。虽然要得出一个函数最终的结果需要先计算出其中的所有未知数,但我们写的程序只需要描述出各未知数以及未知数和已知数之间的逻辑关系。而CPS是手工编排控制流,不是通过定义值之间的关系来解决问题,因此用回调函数编写正确的并发程序很困难。比如在代码清单2中,caculateHead被放在caculateTail的回调中执行,但实际上在计算同一组值时,两者之间并没有依赖关系,只是进入下一轮计算前需要两者都给出结果,但如果不用回调嵌套,实现这种顺序控制比较麻烦。
当然,这和我们的处理方式(共用数组)有关,就这个问题本身而言,caculateHead完全不依赖于任何caculateTail。
这里用的Promis框架是著名的,可以用npm install q安装。虽然,但在它们用法上都大同小异。我们在这里会用到其中的三个方法。
第一个负责将Node.js的CPS函数变成Promise。Node.js核心库和第三方库中有非常多的CPS函数,我们的程序肯定要用到这些函数,要解决回调大坑,就要从这些函数开始。这些函数的回调函数参数大多遵循一个相同的模式,即函数签名为function(err, result)。对于这种函数,可以用简单直接的Q.nfcall和Q.nfapply调用这种Node.js风格的函数返回一个Promise:
return Q.nfcall(FS.readFile, &foo.txt&, &utf-8&);
return Q.nfapply(FS.readFile, [&foo.txt&, &utf-8&]);
也可以用Q.denodeify或Q.nbind创建一个可重用的包装函数,比如:
var readFile = Q.denodeify(FS.readFile);
return readFile(&foo.txt&, &utf-8&);
var redisClientGet = Q.nbind(redisClient.get, redisClient);
return redisClientGet(&user:1:id&);
第二个是then方法,这个方法是Promise对象的核心部件。我们可以用这个方法从异步操作中得到返回值(履约值),或抛出的异常(拒绝的理由)。then方法有两个可选的参数,分别对应Promis对象的两种执行结果。成功时调用的onFulfilled函数,错误时调用onRejected函数:
var promise = asyncFun()
promise.then(onFulfilled, onRejected)
Promise被解决时(异步处理已经完成)会调用onFulfilled 或onRejected 。因为只会有一种可能,所以这两个函数中仅有一个会被触发。尽管then方法的名字让人觉得它跟某种顺序化操作有关,并且那确实是它所承担的职责的副产品,但你真的可以把它当作unwrap来看待。Promise对象是一个存放未知值的容器,而then的任务就是把这个值从Promise中提取出来,把它交给另一个函数。
var promise = readFile()
var promise2 = promise.then(readAnotherFile, console.error)
这个promise表示 onFulfilled 或 onRejected 的返回结果。既然结果只能是其中之一,所以不管是什么结果,Promise都会转发调用:
var promise = readFile()
var promise2 = promise.then(function (data) {
return readAnotherFile() // if readFile was successful, let's readAnotherFile
}, function (err) {
console.error(err) // if readFile was unsuccessful, let's log it but still readAnotherFile
return readAnotherFile()
promise2.then(console.log, console.error) // the result of readAnotherFile
因为then 返回的是Promise,所以promise可以形成调用链,从而避免出现回调大坑:
readFile()
.then(readAnotherFile)
.then(doSomethingElse)
.then(...)
第三个是all和spread方法。我们可以把几个Promise放到一个数组中,用all将它们变成一个Promise,而spread跟在all后面就相当于then,只是它同时接受几个结果。如果数组中的N个Promise都成功,那spread的onFulfilled参数就能收到对应的N个结果;如果有一个失败,它的onRejected就会得到第一个失败的Promise抛出的错误。
下面是用Q改写的解方程程序代码:
var Q = require('q');
var qTail = Q.denodeify(calculateTail);
var qHead = Q.denodeify(calculateHead);
(function calculate(i) {
qTail(res[i],res[i+1]),
qHead(res[i],res[i+1])])
.spread(function(tail,head){
variables[i] =
res[i+1] =
return i+1;
.then(function(i){
if(i === l-1) {
variables[i] = res[i];
process.exit();
calculate(i);
function calculateTail(x,y,cb) {
setTimeout(function(){
var tail = (x-y)/2;
cb(null,tail);
function calculateHead(x,y,cb) {
setTimeout(function(){
var head = (x+y)/2;
cb(null,head);
注意calculateTail和calculateHead中的cb调用,为了满足denodeify的要求,我们给它增加了值为null的err参数。此外还用到了上面提到的denodeify、all和spread、then。其实除了流程控制,Promise在异常处理上也比回调做得好。甚至有些开发团队坚决反对在代码中使用CPS函数,将Promise作为编码规范强制推行。
再来看一下那个找最大文件的例子用Promise实现的样子:
var fs = require('fs')
var path = require('path')
var Q = require('q')
var fs_readdir = Q.denodeify(fs.readdir) // [1]
var fs_stat = Q.denodeify(fs.stat)
module.exports = function (dir) {
return fs_readdir(dir)
.then(function (files) {
var promises = files.map(function (file) {
return fs_stat(path.join(dir,file))
return Q.all(promises).then(function (stats) { // [2]
return [files, stats] // [3]
.then(function (data) { // [4]
var files = data[0]
var stats = data[1]
var largest = stats
.filter(function (stat) { return stat.isFile() })
.reduce(function (prev, next) {
if (prev.size & next.size) return prev
return next
return files[stats.indexOf(largest)]
这时这个模块的用法变成了:
var findLargest = require('./findLargest')
findLargest('./path/to/dir')
.then(function (er, filename) {
console.log('largest file was:', filename)
.fail(function(err){
console.error(err);
因为模块返回的是Promise,所以客户端也变成了Promise风格的,调用链中的所有异常都可以在这里捕获到。不过Q也有可以支持回调风格函数的方法。
generators
generator科普
在计算机科学中,实际上是一种。它很像一个可以返回数组的函数,有参数,可以调用,并且会生成一系列的值。然而generator不是把数组中的值都准备好然后一次性返回,而是一次yield一个,所以它所需的资源更少,并且调用者可以马上开始处理开头的几个值。简言之,generator看起来像函数,但行为表现像迭代器。
Generator也被称为半协程,是协程的一种特例(别协程弱),它总是把控制权交回给调用者(同时返回一个结果值),而不是像协程一样跳转到指定的目的地。如果要说得具体一点儿,就是虽然它们两个都可以yield多次,暂停执行并允许多次进入,但协程可以指定yield之后的去向,而generator不行,它只能把控制权交回给调用者。因为generator主要是为了降低编写迭代器的难度的,所以generator中的yield语句不是用来指明程序要跳到哪里去的,而是用来把值传回给父程序的。
2008年7月,宣布开始ECMAScript Harmony(即ECMAScript 6)项目,从2011年7月开始,ECMAScript Harmony草案开始定期公布,预计到2014年12月正式发布。generator就是,随后不久就。
Node.js对generator的支持是从开始的,但因为Harmony还没正式发布,所以需要指明--harmony或--harmony-generator参数启用它。我们用node --harmony进入REPL,创建一个generator:
function* values() {
for (var i = 0; i & arguments. i++) {
yield arguments[i];
注意generator的定义,用的是像函数可又不是函数的function*,循环时每次遇到yield,程序就会暂停执行。那么暂停后,generator何时会再次执行呢?在REPL中执行o.next():
&var o = values(1, 2, 3);
& o.next();
{ value: 1, done: false }
& o.next();
{ value: 2, done: false }
& o.next();
{ value: 3, done: false }
& o.next();
{ value: undefined, done: true }
第一次执行o.next(),返回了一个对象{ value: 1, done: false },执行到第四次时,value变成了undefined,状态done变成了true。表现得像迭代器一样。next()除了得到generator的下一个值并让它继续执行外,还可以把值传给generator。有些文章提到了send(),不过那是老黄历了,也许你看这篇文章的时候,本文中也有很多内容已经过时了,IT技术发展得就是这样快。我们再看一个例子,还是在REPL中:
function* foo(x) {
yield x + 1;
return x +
再次执行next():
&var f = foo(2);
& f.next();
{ value: 3, done: false }
& f.next();
{ value: null, done: false }
& f.next(5);
{ value: 7, done: true }
注意最后的f.next(5),value变成了7,因为最后一个next将5压进了这个generator的栈中,y变成了5。如果要总结一下,那么generator就是:
yield可以出现在任何表达式中,所以可以随时暂停执行,比如foo(yield x, yield y)或在循环中。
调用generator只是看起来像函数,但实际上是创建了一个generator对象。只有调用next才会再次启动generator。next还可以向generator对象中传值。
generator返回的不是原始值,而是有两个属性的对象:value和done。当generator结束时,done会变成true,之前则一直是false。
可是generator和回调大坑有什么关系呢?因为yield可以暂停程序,next可以让程序再次执行,所以只需稍加控制,就能让异步回调代码顺序执行。
用generator平坑
Node.js社区中有很多借助generator实现异步回调顺序化的库,比如、等,不过我们重点介绍的还是Q。它提供了一个方法。这个方法可以立即运行一个generator,并将其中未捕获的错误发给Q.onerror。比如前面那个解方程的函数,用spawn和generator实现就是:
(function calculate(i) {
Q.spawn(function* () {
i = yield Q.all([qTail(res[i],res[i+1]),qHead(res[i],res[i+1])])
.spread(function(tail,head){
variables[i] =
res[i+1] =
return i+1;
if(i === l-1) {
variables[i] = res[i];
console.log(i + &:& + variables[i]);
process.exit();
calculate(i);
代码和前面用Promise实现时并没有太大变化,只是去掉了then,看起来更简单了。不过记得执行时要用&=v0.11.2版本的Node.js,并且要加上--harmony或--harmony-generator。你会看到和前面相同的结果。至于寻找最大文件那个例子,在spawn里定义一个generator,然后在有then的地方放上yield就行了。具体实现就交给你了。
由于篇幅所限,本文没有展开介绍Promise和generator在错误处理上的优势,这在实际工作中也是很重要的部分,应该认真研究。参考资料7中对此介绍得比较详细,建议认真阅读。
,Marc Harter,
,Marc Harter,
,Andy Wingo,
,James Long,
Author Contacted
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
这篇文章值得收藏
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
找回密码....
InfoQ账号使用的E-mail
关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。
内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。
设置通知机制以获取内容更新对您而言是否重要
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。

我要回帖

更多关于 nodejs 函数返回值 的文章

 

随机推荐