旧笑话新说:曾经有个小偷潜入某神秘机构,偷出机密代码的最后一页,打开一看
});
});
});
});
});
});
});
});
});
});
});
别看别人,node.js,黑的就是你。JavaScript天天被人黑代码难维护,node.js天天被黑回调函数写到吐。不过有时候,语言不是你想换,想换就能换的,我们要寻找方法绕开这几个问题。 有许多第三方库致力于解决回调地狱,像promise、StratifiedJS等等,个人觉得做过头了,也没有让代码变的多可读。下面说说笔者是怎么逃离回调地狱的。
- 采用POJO(Plain Old Java Object)编码风格
-
模拟event delegation(事件委派),源码如下。传入的参数中,agent为对象,func为方法。
function delegate(agent, fn) {
return function() {
return agent[fn].apply(agent, arguments);
}
}
让我们从一个典型的程序开始,看看怎样重构,脱离回调地狱。示例函数包含以下逻辑:
- 将数据从文件里读出来
- 将数据解析成JSON
- 将数据发送至远程HTTP服务器
代码如下:
var fs = require('fs');
// npm: request
var request = require('request');
function testFunc(filename, url, callback) {
// 读文件
fs.readFile(filename, 'utf8', fuction(err, res) {
if (err) {
callback(err);
return;
}
data = {};
// 解析字符串到JSON
try {
data = JSON.parse(res);
} catch (ex) {
callback(ex);
return;
}
// 发送数据至服务器
request.post(url, data, function(err, httpResponse, body) {
if (err) {
callback(err);
} else {
callback(null, body);
}
});
);
}
三个异步操作,带来了两重回调和数个超大的闭包。这样的代码明显是不方便维护的。 下面我们开始重构!首先是引入依赖:
var fs = require('fs');
var request = require('request');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
// 模拟事件委托的函数
function delegate(agent, fn) {
return function() {
return agent[fn].apply(agent, arguments);
}
}
接下来定义程序的主类:
function Handler(filename, url) {
EventEmitter.call(this);
this.filename = filename;
this.url = url;
}
util.inherits(Handler, EventEmitter);
Handler类继承自EventEmitter。接下来是start函数:
Handler.prototype.start = function() {
fs.readFile(this.filename, 'utf8', delegate(this, 'onFileData'));
};
start函数从fs读入数据并将获得的数据传给下个函数:
Handler.prototype.onFileData = function(err, res) {
if (err) {
this.emit('abort', err);
return;
}
data = {};
try {
data = JSON.parse(res);
} catch (ex) {
this.emit('abort', ex);
return;
}
request.post(this.url, data, delegate(this, 'onHttpResp'));
};
onFileData函数处理传进来的数据,发送HTTP POST请求,将结果传给下个函数:
Handler.prototype.onHttpResp = function(err, httpResponse, body) {
if (err) {
this.emit('abort', err);
} else {
this.emit('done', body);
}
};
如此一来,我们封装了一个足够抽象,耦合足够低的一个类,单独放到handler.js文件里吧,主干逻辑就可以简化成:
var Handler = require('./handler');
new Handler('conf.json', 'a.com/upload').on('done', function(body) {
// Success
}).on('abort', function(err) {
// Failed
}).start();
如此一来,无论异步逻辑多复杂,都完全可以只用POJO风格的代码来表达。 如果在done或abort事件时还要接其他异步逻辑,欢迎继续使用delegate。
缺点: 1. POJO编程风格会导致代码量涨上去 2. 这种模拟事件委派的方法使用了执行效率不高的apply方式调用函数 个人觉得为了代码的可读和可维护性,这两点牺牲还是值得的。
delegate函数还可以接着变戏法,我们来加上函数是否存在的判断,再加上try catch。
function delegate(agent, fn) {
return function() {
if (!agent[fn]) {
console.error('function ' + fn + ' does not exist.');
return;
}
try {
return agent[fn].apply(agent, arguments);
} catch (ex) {
console.error('an error occurred: ');
console.error(ex);
console.error('when calling object: ' + agent + ', function: ' + fn);
}
}
}
良好的隔离性出来了,一个函数出错不会影响其他函数,不用在代码里到处加try catch,又方便开发时快速定位错误。发挥想像力,还可以统计函数调用次数等等。 跑题跑远了,就写到这里吧。
|