摘要: 编写上一篇介绍流程控制的文章给我带来了很大的乐趣,现在我想要处理一些反馈,另外还要讨论一下inimino所作的伟大工作。 当前node中有两种处理异步返回值的方法:promises和event emitters。关于两种方法的细节,你可以阅读nodejs.or...
编写上一篇介绍流程控制的文章给我带来了很大的乐趣,现在我想要处理一些反馈,另外还要讨论一下inimino所作的伟大工作。
当前node中有两种处理异步返回值的方法:promises和event emitters。关于两种方法的细节,你可以阅读nodejs.org上的介绍。我将会讨论这两种方法和另一种处理异步返回值和流事件(streaming events)的方法。
为什么要区分Promise和EventEmitter?
在node中有两种处理事件的类,它们是:Promise和EventEmitter。Promise是函数的异步表现形式。
- var File = require(‘file‘);
- var promise = File.read(‘mydata.txt‘);
- promise.addCallback(function (text) {
- // Do something
- });
- promise.addErrback(function (err) {
- // Handle error
- })
File.read接受文件名并返回文件内容。
有时我们需要监听可能多次发生的事件。例如,在一个web服务中,处理web请求时,body事件多次被触发,然后complete事件被触发。
- Http.createServer(function (req, res) {
- var body = "";
- req.addListener(‘body‘, function (chunk) {
- body += chunk;
- });
- req.addListener(‘complete‘, function () {
- // Do something with body text
- });
- }).listen(8080);
这两种方式的不同之处在于:在使用promise时,你会得到success事件或者error事件,但不会同时得到,也不会得到一个以上事件。在处理会发生多次的事件的时候,你就需要更强大的 EventEmitters。
创建自定义promise
假定我想为posix.open, posix.write, 和posix.close写一个便于使用的包装函数filewrite。(如下代码摘自”file”函数库中File.write函数的真实代码)
- function fileWrite (filename, data) {
- var promise = new events.Promise();
- posix.open(filename, "w", 0666)
- .addCallback(function (fd) {
- function doWrite (_data) {
- posix.write(fd, _data, 0, encoding)
- .addCallback(function (written) {
- if (written === _data.length) {
- posix.close(fd);
- promise.emitSuccess();
- } else {
- doWrite(_data.slice(written));
- }
- }).addErrback(function () {
- promise.emitError();
- });
- }
- doWrite(data);
- })
- .addErrback(function () {
- promise.emitError();
- });
- return promise;
- };
filewrite函数可以以如下形式使用:
- fileWrite("MyBlog.txt", "Hello World").addCallback(function () {
- // It’s done
- });
请注意,我必须创建一个promise对象,执行操作,然后将结果传递给这个promise对象。
还有更好的方法
promises工作良好,但是继续读过inimino之后,它所使用的方法令我印象深刻。
是否还记得我们的第一个例子?假设我们按照如下方式使用File.read:
- var File = require(‘file‘);
- File.read(‘mydata.txt‘)(function (text) {
- // Do something
- }, function (error) {
- // Handle error
- });
它不返回promise对象,而是返回一个接受两个回调函数作为参数的函数:一个处理成功,一个处理失败。我把这种风格成为“Do风格”,下面我详细解释:
编写回调风格的代码
如果我们想定义一个不立刻返回值的函数。使用”Do”风格,filewirte函数应当如下使用:(假定之前提到的posix函数也是这个风格)
- function fileWrite (filename, data) { return function (callback, errback) {
- posix.open(filename, "w", 0666)(function (fd) {
- function doWrite (_data) {
- posix.write(fd, _data, 0, encoding)(
- function (written) {
- if (written === _data.length) {
- posix.close(fd);
- callback();
- } else {
- doWrite(_data.slice(written));
- }
- }, errback);
- }
- doWrite(data);
- }, errback);
- }};
请注意,这样很容易就把错误信息返回给了调用者。同时,这种风格也使代码更短,更易阅读。
使用这种风格编写代码的关键是:不要返回promise,而是返回一个接受两个回调的函数,在需要的时候直接调用返回的函数。
“Do”函数库
前些日子我写了一个小型的函数库,叫做“Do”。实际上,它只有一个执行并行操作的函数,就像上一篇文章中介绍的“Combo”库。
实现
如下是整个函数的实现:
- Do = {
- parallel: function (fns) {
- var results = [],
- counter = fns.length;
- return function(callback, errback) {
- fns.forEach(function (fn, i) {
- fn(function (result) {
- results[i] = result;
- counter–;
- if (counter <= 0) {
- callback.apply(null, results);
- }
- }, errback);
- });
- }
- }
- };
结合回调风格,使用这个函数可以写出非常强大和简介的代码。
执行单个操作
我们假定有一个实现了这个新技巧的函数readFIle,可以如下使用这个函数:
- // A single async action with error handling
- readFile(‘secretplans.txt‘)(function (secrets) {
- // Do something
- }, function (error) {
- // Handle Error
- });
执行并行操作
我们继续使用”Do”函数库
- Do.parallel([
- readFile('mylib.js'),
- readFile('secretplans.txt'),
- ])(function (source, secrets) {
- // Do something
- }, function (error) {
- // Handle Error
- });
上述代码代码并行执行了两个异步操作,并在全部执行完毕后执行指定代码。注意,如果没有错误发生,只有处理success的回调函数会被执行。如果出错,函数会将错误传递给通常的错误处理代码。
你也可传递一个文件名数组。
- var files = ["one.txt", "two.txt", "three.txt"];
- var actions = files.map(function (filename) {
- return readFile(filename);
- });
- Do.parallel(actions)(function () {
- var contents = {},
- args = arguments;
- files.forEach(function (filename, index) {
- contents[filename] = args[index];
- });
- // Do something
- });
- // Let error thow exception.
执行顺序操作
要执行顺序操作,只需将函数“串起来”即可:
- readFile(‘names.txt‘)(
- function upcase_slowly(string) { return function (next) {
- setTimeout(function () {
- next(string.toUpperCase());
- }, 100);
- }}
- )(
- function save_data(string) { return function (next) {
- writeFile(‘names_up.txt‘, string)(next);
- }}
- )(function () {
- // File was saved
- });
上述代码读取文件’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语言(还在开发中)的示例代码:
- readFile names.txt
- | fun string -> next ->
- timeout 100, fun ->
- next string.toUpperCase()
- | fun string -> next ->
- writeFile ‘names_up.txt‘, string | next
- | fun ->
- # File was saved