今在我有个学生去面试,面试官问:如何避免回调地狱? 学生回答:使用EventEmitter对象可以避免 面试官:错,不能。要使用Promise。
我的学生回来问我:兴哥EventEmitter真的能解决回调地狱吗? 我不知道怎么回答,因为我一直用EventEmitter来编写异步回调的。node4.X版本中的文件操作不都是支持EventEmitter吗?
exports.copyStream = function() {
var et = new events.EventEmitter();
et.on("cpstart",function() {
fs.open("home.js","r",function( err,fd ) {
if( err ) {
return console.log(err);
}
console.log("打开home.js文件成功");
et.emit("cpread",fd);
});
}).on("cpread",function( fd ) {
var buf = new Buffer(1024);
fs.read(fd,buf,0,buf.length,0,function( err, bytes ){
if( err ) {
return console.log(err);
}
console.log("一共读取了" + bytes + "个字节");
et.emit("cpwrite","home_stream_1.js",buf,parseInt(bytes));
et.emit("cpclose",fd,"home.js文件");
});
}).on("cpclose",function( fd,msg ){
fs.close(fd,function( err ) {
if( err ) {
return console.log(err);
}
console.log(msg + "关闭成功");
});
}).on("cpwrite",function(filename, buf, bytes ) {
fs.open(filename,"w",function( err,fd ) {
if( err ) {
return console.log(err);
}
console.log("打开" + filename + "成功");
fs.write(fd,buf,0,bytes,function( err,written ) {
if( err ) {
return console.log(err);
}
console.log("一共写入了" + written + "个字节");
et.emit("cpclose",fd,filename + "文件");
});
});
}).emit("cpstart");
}
这样算不算解决了回调地狱的问题,大家教教我。
EventEmitter只能较初级地解决callback hell。
唉,这让我想起在我写Java那会,Hibernate大行其道,所有人都去使用HQL时,好多人忘了JDBC如何去写。在Spring大行其道时,又有好多人回归到JDBC的怀抱。 说实在的,我并没有觉得使用了Promise之后,回调就晰了。我个人认为,Promise只有二种状态,其实并不能完美解决好多问题。反而EventEmitter给了我们编写代码时的灵活性。 其实说白了,就是如何组织回调链的问题,想清楚,用什么方法实现不了呢。
mk
不过,尽管 PubSub 模式如此多 才多艺,但它不是适用于所有任务的万能工具。PubSub 模式尤其不 适用于一次性事件,一次性事件要求对异步函数执行的一次性任务的 两种结果(完成任务或任务失败)做不同的处理。(Ajax 请求就是常 见的一次性事件实例。)用于解决一次性事件问题的工具叫做 Promise,这正是下一章的主题。 ——《Javascript异步编程》
推荐读一下
@sleelily 谢谢分享 我并不排斥Promise,它的存在必然是解决某些问题的。
@zouzhenxing 说得好!
这不是很优美吗?那个面试的水平有限。
@JacksonTian 我现在绕了一圈,还是喜欢用 eventproxy
@alsotang 避免回调地狱的方案其实挺多的。event emitter无疑还是最底层的一种,但很多场景还是离不开它。
@JacksonTian promise话太多,async/co 很多时候还是要用 promise 包一层 api。
反正所有 api 都支持 callback,就干脆继续 eventproxy 了。
@JacksonTian 而且 promise 当 chain 起来的时候,要保证自己不会漏了错误处理。要按 promise 的最佳实践把各个 promise 合理地串起来,心智负担也挺重的。
EventEmitter和Promise都可以解决callback hell,个人觉得Promise从形式上来说更加像阻塞的代码,更加容易读。 如果你的业务涉及多个异步调用,并且它们之间有先后依赖关系的话(例如先请求网络,然后写数据库,最后写本地文件),用EventEmitter实现会让逻辑不连贯。但是用promise的话就很好看了,它会显示成这样
sendRequest()
.then(writeDB)
.then(writeFile)
catch(error)
如果用EventEmitter的话就或者会变成这样:
sendRequest.on("end", ()=>e.emit("writeDB"))
writeDB.on("writeDB", ()=>e.emit("writeFile"))
writeFile.on("writeFile", ()=>{})
比较一下明显promise更加易读。
如果一个异步调用返回了EventEmitter,我会觉得神烦,如果返回了一个promise我会觉得很爽,可能是因为我在项目中大量使用了promise的缘故,已经习惯了promise的开发方式。
那个面试的水平有限。
@JacksonTian 主要代码写起来比较简单,不用去理解promise这种概念…就是有时候有些冗长~
我感觉promise比较好的地方在于,作为变量可以被传递。
暂时用Co+Promise+Generator这样写起来很清晰,像同步的写法,到ES7就Async/Await
mk