避免节点中的回调地狱

时间:2018-04-01 22:51:04

标签: javascript node.js express asynchronous

我有这个功能

/* GET main page */
router.get('/', (req, res, next) => {
    Account.find({accType: 'Employer'}, (err, col) => {
        if (err) {
            console.log(err);
        } else {
            Banner.findOne((err, banner) => {
                if (err) {
                    console.log(err);
                } else {
                    // Determine whether there is at least one awaiting account.
                    Account.findOne({accType: 'Awaiting'}, (err, doc) => {
                            if (err) {
                                console.log(err);
                            } else {
                                if (doc != null) {
                                    var awaiting = true;
                                }
                                console.log(doc);
                                res.render('admin/', {
                                    title: 'Admin pannel',
                                    user: req.user,
                                    employers: col,
                                    areAwaiting: awaiting,
                                    banner: banner,
                                )
                            }
                        }
                    );
                }
            });
        }
    });
});

我尝试使用async模块,如下所示:

async.parallel(calls, (err, col) => {
    if (err)
        console.log(err);
    else {
        console.log(col);
        res.render('admin/', {
            title: 'Admin pannel',
            user: req.user,
            employers: col[0],
            banner: col[1],
            areAwaiting: col[2],
        })
    }
});

现在。我得到的错误是wrapAsync(...) is not a function。我尝试将我的函数放在这样的匿名函数中:

() => {
    Account.find({accType: 'Employer'}, (err, col) => {
        if (err)
            console.log(err);
        else return col;
    })
}

然后代码就冻结了。我也试过这个:

(col) => {
    Banner.findOne((err, doc) => {
        if (err)
            console.log(err);
        else return doc;
    });
    return col
},

但效果相同。知道我做错了什么吗?回调地狱版本有效,但只是丑陋且无法维护。

Dave Meehan的回答。不得不稍微编辑它以使其工作。

router.get('/', (req, res) => {
    return Promise.all([
        Account.find(),
        Banner.findOne(),
        Account.findOne({accType: 'Awaiting'}),
    ]).then(([employers, banner, awaiting]) => { // Right here
        if (awaiting != null)
            var areAwaiting = true;
        res.render('admin/', {
            title: 'Admin pannel',
            user: req.user,
            employers: employers,
            areAwaiting: areAwaiting,
            banner: banner,
        });
    }).catch(e => {
        console.error(e)
    });
});

我必须将then中的数组关闭到()

2 个答案:

答案 0 :(得分:1)

总是可以通过不将其写为回调地狱来避免回调地狱。但在这种情况下,让我们先重写一些代码。

首先,如果收到错误,您可以提前纾困,因此无需在所有通话中使用else语句。这将为你删除很多范围。

因此,只需重写部分代码,就可以将其转换为如下所示:

router.get('/', (req, res, next) => {                       
  getData((err, data) => {                                  
    if (err) return console.error(err)                                        
    res.render('admin/', { /* .. */ })                      
  })                                                        
})                                                          

function getData (cb) {                                     
  Account.find({accType: 'Employer'}, (err, col) => {       
    if (err) return cb(err)                                 
    Banner.findOne((err, banner) => {                       
      if (err) return cb(err)                               
      Account.findOne({accType: 'Awaiting'}, (err, doc) => {
        if (err) return cb(err)                             
        cb(null, { col, banner, doc })                      
      })                                                    
    })                                                      
  })                                                        
}                                                           

现在,让我们回到您对async的使用,这在这里使用是个好主意,因为我真的不想拨打Account.find()Banner.findOne()Account.findOne()按顺序。

你的问题在于你错误地使用async.parallel,它假定一个函数的数组(或对象)进行回调。

这看起来如下:

async.parallel({                                       
  col: cb => Account.find({accType: 'Employer'}, cb),  
  banner: cb => Banner.findOne(cb),                    
  doc: cb => Account.findOne({accType: 'Awaiting'}, cb)
}, (err, result) => {                                  
  // access result.col, result.banner and result.doc   
})                                                     

现在我们可以在上面重构getData,因此最终结果可能如下:

router.get('/', (req, res, next) => {                              
  getData((err, data) => {                                         
    if (err) return console.error(err)                                               
    res.render('admin/', { /* data.doc, data.banner, data.doc */ })
  })                                                               
})                                                                 

function getData (cb) {                                            
  async.parallel({                                                 
    col: cb => Account.find({accType: 'Employer'}, cb),            
    banner: cb => Banner.findOne(cb),                              
    doc: cb => Account.findOne({accType: 'Awaiting'}, cb)          
  }, cb)                                                           
}                                                                  

这看起来非常干净,并且确实没有必要使用Promises。

答案 1 :(得分:1)

这很简单,并假设:

  1. 您使用Express或类似的路由处理程序,并且您可以返回承诺而不是使用回调

  2. 您使用的是Mongoose或类似的数据库,可以返回承诺而不是使用回调。

  3. 检查您的版本是否支持Promise。

    您似乎至少缺少Banner的一些查询参数,如果其中任何一个依赖,或者如图所示,它们可以并行运行,您需要确定它们。

    router.get('/', (req, res) => {
      return Promise.all([
        Account.find(),
        Banner.findOne(),
        Account.findOne({ accType: 'Awaiting' }),
      ]).then(([ col, banner, doc ]) => {
        res.render('admin/', {
          title: 'Admin pannel',
          user: req.user,
          employers: col,
          areAwaiting: awaiting,
          banner: banner,
        );
      }).catch(e => { console.error(e) });
    });