分享

逃离回调地狱

 心然之月 2016-05-24

旧笑话新说:曾经有个小偷潜入某神秘机构,偷出机密代码的最后一页,打开一看

                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });
    });
});

别看别人,node.js,黑的就是你。JavaScript天天被人黑代码难维护,node.js天天被黑回调函数写到吐。不过有时候,语言不是你想换,想换就能换的,我们要寻找方法绕开这几个问题。
有许多第三方库致力于解决回调地狱,像promise、StratifiedJS等等,个人觉得做过头了,也没有让代码变的多可读。下面说说笔者是怎么逃离回调地狱的。

  1. 采用POJO(Plain Old Java Object)编码风格
  2. 模拟event delegation(事件委派),源码如下。传入的参数中,agent为对象,func为方法。

     function delegate(agent, fn) {
         return function() {
             return agent[fn].apply(agent, arguments);
         }
     }

让我们从一个典型的程序开始,看看怎样重构,脱离回调地狱。示例函数包含以下逻辑:

  1. 将数据从文件里读出来
  2. 将数据解析成JSON
  3. 将数据发送至远程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,又方便开发时快速定位错误。发挥想像力,还可以统计函数调用次数等等。
跑题跑远了,就写到这里吧。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多