Skip to content

08 如何避免回调地狱

孙正华 edited this page Jul 26, 2018 · 3 revisions

Node.js 是单线程的,任何耗时的操作都应该使用异步去执行。
JavaScript 中的异步实现常见的有回调函数和 Promise,这里有一篇很好的学习 Promise 的中文教程
回调函数应该是异步实现最基本的方式,但很容易写出类似的代码:

fn1(function(data1){
    fn2(data1, function(data2){
        fn3(data2, function(){
            //...
        });
    });
});

当嵌套层级越来越多,代码将趋于>形,难以阅读和维护,于是有人称之为回调地狱。此时可以使用 async 来解决这个问题(如果你的浏览器或 Node.js 版本较高,你也可以直接使用原生 Promise)。
安装 async:

$ npm install --save async

async 最常用的 2 个方法介绍:

  • parallel 异步执行各个方法,方法间不能有依赖。
async.parallel([
    //异步方法1
    function(callback){
        setTimeout(function(){
            //执行结果为 'one'
            callback(null, 'one');
        }, 200);
    },
    //异步方法2
    function(callback){
        setTimeout(function(){
            //执行结果为 'two'
            callback(null, 'two');
        }, 100);
    }
],
//可选,全部异步方法执行完成后回调
function(err, results){
    //results 是执行结果的数组,按照上面异步方法1、异步方法2的定义的顺序,此处 results = ['one','two']
});

实例:

https://github.com/eshengsky/iBlog2/blob/master/routes/blog.js#L16-L46

async.parallel([
    // 获取配置
    function (cb) {
        tool.getConfig(path.join(__dirname, '../config/settings.json'), (err, settings) => {
            if (err) {
                cb(err);
            } else {
                cb(null, settings);
            }
        });
    },

    // 获取分类
    function (cb) {
        category.getAll((err, categories) => {
            if (err) {
                cb(err);
            } else {
                cb(null, categories);
            }
        });
    }
], (err, results) => {
    let settings,
        categories,
        cate;
    if (err) {
        next(err);
    } else {
        settings = results[0];
        categories = results[1];
    }
});
  • waterfall 依次执行任务数组中的方法,并将前一个方法的返回值作为参数传入下一个方法。
async.waterfall([
    //方法1
    function(callback) {
        //返回结果 'one','two',作为下一个方法的参数
        callback(null, 'one', 'two');
    },
    //方法2,其中 arg1 = 'one',arg2 = 'two'
    function(arg1, arg2, callback) {
        //返回结果 'three',作为下一个方法的参数
        callback(null, 'three');
    },
    //方法3,其中 arg1 = 'three'
    function(arg1, callback) {
        //返回结果 'done',作为最终回调函数的参数
        callback(null, 'done');
    }
], function (err, result) {
    //result = 'done'
});

实例:

https://github.com/eshengsky/iBlog2/blob/master/routes/blog.js#L68-L124

async.waterfall([
    // 1. 根据分类alias获取分类对象
    function (cb) {
        category.getByAlias(req.body.CateAlias, (err, category) => {
            if (err) {
                cb(err);
            } else {
                cb(null, category);
            }
        });
    },
    // 2. 传入分类对象查询文章
    function (category, cb) {
        const params = {
            cateId: category._id,
            pageIndex: req.body.PageIndex,
            pageSize: req.body.PageSize,
            sortBy: req.body.SortBy,
            keyword: req.body.Keyword,
            filterType: req.body.FilterType
        };
        //...
    }
], (err, result) => {
    if (err) {
        cb(err);
    } else {
        cb(null, result);
    }
});