jser都知道,由于异步特性,在nodejs里,全是callback!!!一个不小心,下面的代码就会冒出来啦~~~
obj.func0( data0, function func1(data1){
obj.func2( data1, function func3(data3){
obj.func4( data3, function func5(data5){
obj.func6( data5, function func7(data7){
obj.func8( data7, function func9(data9){
...
});
});
});
});
});
如果作为维护人员的你,看到这种代码,请放声哭泣吧~~~因为你遇到了 callback hell 啦!遇见地狱都不哭,那什么时候哭?
因此,作为开发人员的你,是否有必要放维护人员一条生路?救人一命,胜造七级浮屠,相信留人一命也有同样的效果哦。下面,就让我们以复制一个文件为例,来研究一下如何放下屠刀吧 ^_^
除了第一个主文件(入口文件),以下文件的命名均为 duplicate.js,存放于src目录下
主文件 index.js :
var hello = require("./src/duplicate.js"); // hello 对象依赖于duplicate.js文件
hello.copy("./src/duplicate.js","./duplicate_c.js"); //该函数对应duplicate.js里面的exports.copy
这是原生的代码模样 duplicate.js:
'use strict'; //以下代码需要使用严格模式
var fs = require("fs"); //引入文件模块(nodejs自带模块)
// ( args ) => {} 是ES6的函数的一种模式(俗称箭头函数),作用是静态绑定函数内部的this,使this的指向不再发生变化
exports.copy = (source,target) => { //source代表要复制的文件的路径,target则代表复制到的路径
fs.open(source,"r",(err,fdr) => { //以只读的方式打开源文件并获取文件句柄 fdr ,当然,原生回调中都以 错误返回变量 作为首参
if( err ){ //如果返回了错误信息
console.log( "打开源文件失败:" + err );
return;
}
var buffer = new Buffer(2048); //在内存中开辟一块2048字节大小的缓存区
fs.read(fdr,buffer,0,buffer.length,0,(err,rSize,buffer) => { //通过文件句柄 fdr 把文件流读到缓存中
if( err ){
console.log( "读取源文件失败:" + err );
return;
}
//console.log( buffer.slice(0,rSize).toString() );
fs.open(target,"w+",(err,fdw) => { //当读取源文件成功后,再以写(如果不存在就创建新的文件)的方式打开目标文件,并获取其句柄 fdw
if( err ){
console.log( "打开目标文件失败:" + err );
return;
}
fs.write(fdw,buffer,0,rSize,( err, wSize, buffer ) => { //把缓冲区的文件流以固定格式写入到 fdw 所指向的目标文件中
if( err ){
console.log( "写入目标文件失败:" + err );
return;
}
console.log("写入文件成功,共写入" + wSize + "个字节");
})
fs.close(fdw,(err) => { //通过句柄关闭文件
if( err ){
console.log( "关闭目标文件失败:" + err );
}
});
});
fs.close(fdr,(err) => { //通过句柄关闭文件
if( err ){
console.log( "关闭源文件失败:" + err );
}
});
});
});
}
以上代码量还是挺少的。但是,算上 if 语句的层数,已经达到了七层之多,还是蛮吓人的哦。那么,我们如何减少代码层数,让伦家看起来不辣么累呢?继续上代码!
一、nodejs自带的 EventEmitter() 方法(生产者与消费者模式)duplicate.js:
'use strict'; //以下代码需要使用严格模式
var fs = require("fs"); //引入文件模块(nodejs自带模块)
var events = require("events"); //引入事件模块(nodejs自带模块)
exports.copy3 = ( source, target ) => {
var em = new events.EventEmitter(); // em 是由EventEmitter类 new 出来的一个对象,那么下面就可通过该对象声明以及跳转到不同的 cb回调函数中去
em.on("opensource",() => { // em.on("cbName") 可声明一个回调函数的位置,然后再通过 em.emit("cbName") 来执行该回调函数
fs.open( source, "r", ( err, fd ) => {
if( err ){
console.log("打开源文件失败:",err.stack);
return;
}
em.emit("readsource",fd); //执行 “readsource” 回调函数,注意参数要对应好
});
}).on("readsource",( fd )=>{ //声明 “readsource” 回调函数
var buffer = new Buffer(2048);
fs.read( fd, buffer, 0, buffer.length, 0, ( err, readsize, buffer ) => {
if( err ){
console.log("读取源文件失败:",err.stack);
return;
}
em.emit("opentarget", readsize, buffer);
});
fs.close( fd,( err )=>{
if( err ){
console.log("关闭源文件失败:");
}
});
}).on("opentarget",( readsize, buffer )=>{
fs.open( target, "w+", ( err, fd ) => {
if( err ){
console.log("打开目标文件失败:",err.stack);
return;
}
em.emit("writesource", fd, readsize, buffer );
});
}).on("writesource",( fd, readsize, buffer )=>{
fs.write( fd, buffer, 0, readsize, ( err, writesize, buffer ) => {
if( err ){
console.log("读取目标文件失败:",err.stack);
return;
}
console.log("一共写入了",writesize,"个字节");
});
fs.close( fd,( err )=>{
if( err ){
console.log("关闭目标文件失败:");
}
});
}).emit("opensource"); //在最后这里 emit("opensource") 以使该系列回调函数能够被调用到,这里相当于入口
}
可以看出,该模式明显减少了代码的层数,使代码的结构变得清晰。但是,该段代码也是有问题的?问题在哪呢?由于时间关系,本文无法一次写完,敬请期待我的更新吧!!!