Express新手求助:如何用Promise实现MongoDB Find查询顺序执行?
解决MongoDB异步查询的顺序执行问题
嘿,刚接触Express和MongoDB的话,异步操作的顺序确实容易让人头疼!你现在的问题是两个find查询并行执行,导致第二个查询里的length变量还没被第一个查询赋值,分页计算自然就出错了。其实MongoDB的驱动已经支持Promise了,我们可以用Promise链式调用或者更直观的async/await来解决这个问题,后者对新手来说更容易理解。
为什么你的代码会出问题?
你现在用的是回调函数写法,两个exec()会同时发起数据库请求,Node.js不会等第一个查询完成再执行第二个。所以当第二个查询里用到length的时候,第一个查询的回调还没执行,length还是初始的undefined,这就导致后续的numOfProducts也是undefined,分页计算肯定不对。
解决方案:用async/await简化异步流程
async/await是ES2017的语法,能让异步代码看起来像同步代码,非常适合处理这种需要顺序执行的异步操作。我们只需要把路由处理函数改成async函数,然后用await等待每个查询完成即可。
修改后的代码示例:
app.get("/cryptonews/:page", async function (req, res) { try { let page = req.params.page; const perpage = 10; var page_name; // 第一个查询:获取总数据量,等待查询完成后再往下执行 const allNews = await news.find().sort({newsDate: -1}).exec(); console.log("1st"); const length = allNews.length; console.log("length2", length); // 第二个查询:获取分页数据(此时第一个查询已经完成,length有正确值) const paginatedNews = await news.find() .sort({newsDate: -1}) .skip((perpage * page) - perpage) .limit(perpage) .exec(); console.log("2nd"); const numOfProducts = length; console.log("length", numOfProducts); // 后续的渲染逻辑保持不变,只是把news变量换成paginatedNews if(req.session.user != null){ if(req.session.user.address){ const smartContrat = this.web3.eth.contract(this.abi).at(contractAddress); const balance = smartContrat.balanceOf(req.session.user.address) / 1e18; res.render('cryptonews', { sess: req.session, news: paginatedNews, imageurl, balance: balance, currentPage: page, pages: Math.ceil(numOfProducts / perpage), numOfResults: numOfProducts, page_name:'news' }); } else{ res.render('cryptonews', { sess: req.session, news: paginatedNews, imageurl, currentPage: page, pages: Math.ceil(numOfProducts / perpage), numOfResults: numOfProducts, page_name:'news' }); } } else{ res.render('cryptonews', { sess: req.session, news: paginatedNews, imageurl, currentPage: page, pages: Math.ceil(numOfProducts / perpage), numOfResults: numOfProducts, page_name:'news' }); } } catch (err) { // 统一捕获所有异步操作的错误,避免服务器崩溃 console.error(err); res.status(500).send('服务器出错了'); } });
关键说明:
- async函数:把路由处理函数声明为
async function,这样里面就可以使用await关键字。 - await关键字:每个
await都会等待后面的Promise完成,所以第一个查询完成后才会执行第二个查询,length就能正确获取到总数据量。 - 错误处理:用
try/catch包裹所有异步操作,这样任何查询出错都会被捕获,我们可以统一返回错误响应。
如果你想理解Promise的基础用法,也可以用链式调用的写法:
app.get("/cryptonews/:page", function (req, res) { let page = req.params.page; const perpage = 10; var page_name; // 第一个查询返回Promise,用then处理结果 news.find().sort({newsDate: -1}).exec() .then(allNews => { console.log("1st"); const length = allNews.length; console.log("length2", length); // 第一个查询完成后,返回第二个查询的Promise return news.find() .sort({newsDate: -1}) .skip((perpage * page) - perpage) .limit(perpage) .exec() .then(paginatedNews => ({ paginatedNews, length })); }) .then(({ paginatedNews, length }) => { console.log("2nd"); const numOfProducts = length; console.log("length", numOfProducts); // 这里写渲染逻辑,和之前一致 }) .catch(err => { console.error(err); res.status(500).send('服务器出错了'); }); });
不过对于新手来说,async/await的写法可读性更高,推荐优先使用。
内容的提问来源于stack exchange,提问作者Dv Thiyanesh
相关产品推荐
相关产品推荐

