龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > web编程 > Javascript编程 >

浅析Node.js中的流程控制(1)(2)

时间:2013-03-06 14:58来源:未知 作者:admin 点击:
分享到:
上一篇介绍流程控制的文章给我带来了很大的乐趣,现在我想要处理一些反馈,另外还要讨论一下inimino所作的伟大工作。 当前node中有两种处理异步返回值

上一篇介绍流程控制的文章给我带来了很大的乐趣,现在我想要处理一些反馈,另外还要讨论一下inimino所作的伟大工作。

当前node中有两种处理异步返回值的方法:promises和event emitters。关于两种方法的细节,你可以阅读nodejs.org上的介绍。我将会讨论这两种方法和另一种处理异步返回值和流事件(streaming events)的方法。

为什么要区分Promise和EventEmitter?

在node中有两种处理事件的类,它们是:Promise和EventEmitter。Promise是函数的异步表现形式。

  1. var File = require('file');  
  2. var promise = File.read('mydata.txt');  
  3. promise.addCallback(function (text) {  
  4.   // Do something  
  5. });  
  6. promise.addErrback(function (err) {  
  7.   // Handle error  
  8. })  

File.read接受文件名并返回文件内容。

有时我们需要监听可能多次发生的事件。例如,在一个web服务中,处理web请求时,body事件多次被触发,然后complete事件被触发。

  1. Http.createServer(function (req, res) {  
  2.   var body = "";  
  3.   req.addListener('body'function (chunk) {  
  4.     body += chunk;  
  5.   });  
  6.   req.addListener('complete'function () {  
  7.     // Do something with body text  
  8.   });  
  9. }).listen(8080);  

这两种方式的不同之处在于:在使用promise时,你会得到success事件或者error事件,但不会同时得到,也不会得到一个以上事件。在处理会发生多次的事件的时候,你就需要更强大的 EventEmitters。

创建自定义promise

假定我想为posix.open, posix.write, 和posix.close写一个便于使用的包装函数filewrite。(如下代码摘自”file”函数库中File.write函数的真实代码)

  1. function fileWrite (filename, data) {  
  2.   var promise = new events.Promise();  
  3.   posix.open(filename, "w", 0666)  
  4.     .addCallback(function (fd) {  
  5.       function doWrite (_data) {  
  6.         posix.write(fd, _data, 0, encoding)  
  7.           .addCallback(function (written) {  
  8.             if (written === _data.length) {  
  9.               posix.close(fd);  
  10.               promise.emitSuccess();  
  11.             } else {  
  12.               doWrite(_data.slice(written));  
  13.             }  
  14.           }).addErrback(function () {  
  15.             promise.emitError();  
  16.           });  
  17.       }  
  18.       doWrite(data);  
  19.     })  
  20.     .addErrback(function () {  
  21.       promise.emitError();  
  22.     });  
  23.   return promise;  
  24. };  

filewrite函数可以以如下形式使用:

  1. fileWrite("MyBlog.txt""Hello World").addCallback(function () {  
  2.   // It's done  
  3. });  

请注意,我必须创建一个promise对象,执行操作,然后将结果传递给这个promise对象。

还有更好的方法

promises工作良好,但是继续读过inimino之后,它所使用的方法令我印象深刻。

是否还记得我们的第一个例子?假设我们按照如下方式使用File.read:

  1. var File = require('file');  
  2. File.read('mydata.txt')(function (text) {  
  3.   // Do something  
  4. }, function (error) {  
  5.   // Handle error  
  6. });  

它不返回promise对象,而是返回一个接受两个回调函数作为参数的函数:一个处理成功,一个处理失败。我把这种风格成为“Do风格”,下面我详细解释:

编写回调风格的代码

如果我们想定义一个不立刻返回值的函数。使用”Do”风格,filewirte函数应当如下使用:(假定之前提到的posix函数也是这个风格)

  1. function fileWrite (filename, data) { return function (callback, errback) {  
  2.   posix.open(filename, "w", 0666)(function (fd) {  
  3.     function doWrite (_data) {  
  4.       posix.write(fd, _data, 0, encoding)(  
  5.         function (written) {  
  6.           if (written === _data.length) {  
  7.             posix.close(fd);  
  8.             callback();  
  9.           } else {  
  10.             doWrite(_data.slice(written));  
  11.           }  
  12.         }, errback);  
  13.     }  
  14.     doWrite(data);  
  15.   }, errback);  
  16. }};  

请注意,这样很容易就把错误信息返回给了调用者。同时,这种风格也使代码更短,更易阅读。

使用这种风格编写代码的关键是:不要返回promise,而是返回一个接受两个回调的函数,在需要的时候直接调用返回的函数。

“Do”函数库

前些日子我写了一个小型的函数库,叫做“Do”。实际上,它只有一个执行并行操作的函数,就像上一篇文章中介绍的“Combo”库。

实现

如下是整个函数的实现:

  1. Do = {  
  2.   parallel: function (fns) {  
  3.     var results = [],  
  4.         counter = fns.length;  
  5.     return function(callback, errback) {  
  6.       fns.forEach(function (fn, i) {  
  7.         fn(function (result) {  
  8.           results[i] = result;  
  9.           counter--;  
  10.           if (counter <= 0) {  
  11.             callback.apply(null, results);  
  12.           }  
  13.         }, errback);  
  14.       });  
  15.     }  
  16.   }  
  17. };  

结合回调风格,使用这个函数可以写出非常强大和简介的代码。

执行单个操作

我们假定有一个实现了这个新技巧的函数readFIle,可以如下使用这个函数:

  1. // A single async action with error handling  
  2. readFile('secretplans.txt')(function (secrets) {  
  3.   // Do something  
  4. }, function (error) {  
  5.   // Handle Error  
  6. });  

执行并行操作

我们继续使用”Do"函数库

  1. Do.parallel([  
  2.     readFile('mylib.js'),  
  3.     readFile('secretplans.txt'),  
  4. ])(function (source, secrets) {  
  5.   // Do something  
  6. }, function (error) {  
  7.   // Handle Error  
  8. });  

上述代码代码并行执行了两个异步操作,并在全部执行完毕后执行指定代码。注意,如果没有错误发生,只有处理success的回调函数会被执行。如果出错,函数会将错误传递给通常的错误处理代码。

你也可传递一个文件名数组。

  1. var files = ["one.txt""two.txt""three.txt"];  
  2. var actions = files.map(function (filename) {  
  3.   return readFile(filename);  
  4. });  
  5.  
  6. Do.parallel(actions)(function () {  
  7.   var contents = {},  
  8.       args = arguments;  
  9.   files.forEach(function (filename, index) {  
  10.     contents[filename] = args[index];  
  11.   });  
  12.   // Do something  
  13. });  
  14. // Let error thow exception.  

执行顺序操作

要执行顺序操作,只需将函数“串起来”即可:

  1. readFile('names.txt')(  
  2.   function upcase_slowly(string) { return function (next) {  
  3.     setTimeout(function () {  
  4.       next(string.toUpperCase());  
  5.     }, 100);  
  6.   }}  
  7. )(  
  8.   function save_data(string) { return function (next) {  
  9.     writeFile('names_up.txt', string)(next);  
  10.   }}  
  11. )(function () {  
  12.   // File was saved  
  13. });  

上述代码读取文件'names.txt',完成之后调用upcase_slowly,然后将生成的新字符串专递给save_data函数。save_data函数是对writeFile的一个包装。当save_data函数执行完毕之后,将执行最终的回调函数。

Just for fun, here is the same example translated to the Jack language (still in development).

开个玩笑,如下代码是翻译成Jack语言(还在开发中)的示例代码:

  1. readFile names.txt  
  2. | fun string -> next ->  
  3.   timeout 100, fun ->  
  4.     next string.toUpperCase()  
  5. | fun string -> next ->  
  6.   writeFile 'names_up.txt', string | next  
  7. | fun ->  
  8.  # File was saved  

原文:http://www.grati.org/?cat=35

精彩图集

赞助商链接