【问题标题】:Avoiding callback hell in node避免节点中的回调地狱
【发布时间】:2018-09-11 04:51:58
【问题描述】:

我有这个功能

/* 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
},

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

戴夫·米汉的回答。必须稍作修改才能使其正常工作。

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 中的数组关闭为()

【问题讨论】:

  • 如果你在服务器端(并且你的类支持 Promises),并且有最新的 NodeJS LTS 版本可用,你可以使用原生的 async / await 语言特性。见blog.risingstack.com/mastering-async-await-in-nodejs
  • 它是否适用于我的查询?所有异步文档都显示 API 调用
  • 如果有一个很好的回调地狱的例子以及为什么要使用承诺链,这就是一个。切换到 Promise,而不是异步库。
  • 而且,您的数据库很可能也已经支持 Promise(看起来可能是 mongodb 已经支持 Promise)。
  • 我很难理解承诺链。我编辑了我的问题以包含我的试用版

标签: javascript node.js express asynchronous


【解决方案1】:

这很简单,并且假设:

  1. 您正在使用 Express 或类似的路由处理程序,您可以返回 Promise 而不是使用回调

  2. 您正在使用 Mongoose 或类似数据库,并且可以返回一个承诺而不是使用回调。

检查您的版本以获得 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) });
});

【讨论】:

  • 当我试图实现这个时,我得到:/home/iron/Documents/school/wps/Project/routes/admin.js:32 ]).then([ col, banner, doc ] => { ^ SyntaxError: Unexpected token ] 知道可能是什么原因吗?
  • 稍作修改后使其工作。查看工作版本的帖子
  • 对不起,是的,我错过了箭头函数上的括号。现已编辑。
【解决方案2】:

回调地狱总是可以通过不把它写成回调地狱来避免的。不过,在这种情况下,让我们从重写一些代码开始。

首先,如果您收到错误消息,您可以提前退出,因此无需在所有调用中使用 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。

【讨论】:

  • 当我尝试实现这个时,我得到Error: Callback was already called. 知道可能是什么原因吗?
猜你喜欢
  • 2017-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-01
  • 2017-08-18
  • 2016-03-27
  • 1970-01-01
相关资源
最近更新 更多