递归Redis函数中的async/await使用问题排查
Redis-om递归遍历循环链的两个问题
我是async/await新手,现在要用Redis-om写递归函数NN_walkd遍历Redis数据库查找循环链,遇到两个问题:
- 我在内部递归调用
NN_walkd时用的async/await写法对吗? - 运行时
compSearchM能正常调用(返回5条数据,需要调用5次NN_walkd),但递归调用NN_walkd后第一次循环调用compSearchK时,卡在了第一个Redis的.search调用上,而compSearchK和compSearchM代码结构基本一致。
相关代码
主调用
NN_walk = async function(req, db, cnode, pnode, chain, cb) { var vegas, sneaker; req.session.walk = []; await NN_walkd(req, cnode, pnode, [], 1); req.session.walk = null; console.log('~~~~~~~~~~~~ Out of Walk ~~~~~~~~~~~~~~~'); cb(); };
redis.mjs
export class RedisDB { constructor() { ... this._companyRepo = ... } compSearchK(ckey) { // doesn't matter if I have a async or not here return new Promise(async (resolve) => { const sckey = await this._companyRepo.search() .where('COMPANYKEY').equals(ckey) .return.all(); if (sckey.length) { const ttrr = await this._companyRepo.fetch(sckey[0].entityId); resolve(ttrr.toJSON()); } else resolve(null); }); } compSearchM(mkey) { var tArr=[]; return new Promise(async (resolve) => { const smkey = await this._companyRepo.search() .where('MASTERKEY').equals(mkey) .and('TBLNUM').equals(10) .return.all(); if (smkey.length) { for (var spot in smkey) { const ttrr = await this._companyRepo.fetch(smkey[spot].entityId); tArr.push(ttrr.toJSON()); } resolve(tArr); } else { resolve(null); } }); } }
walk.js
NN_walkd = async function(req, cnode, pnode, chain, lvl) { ... if (cnode[1]) { const sObj = await req.app.get('redis').compSearchK(cnode[1]); if (sObj) { int1 = (sObj.TBLNUM==1) ? null : sObj.CLIENTKEY; (async () => await NN_walkd(req, [sObj.COMPANYKEY,int1], cnode, Array.from(chain), tlvl))() } } else { const sArr = await req.app.get('redis').compSearchM(cnode[0]); if (sArr.length) { for (sneaker in sArr) { (async () => await NN_walkd(req, [sArr[sneaker].COMPANYKEY,sArr[sneaker].CLIENTKEY], cnode, Array.from(chain), tlvl))() } } else { console.log('no more links on this chain: ',cnode); } } }
问题解答
1. 递归调用的async/await写法错误
你当前用立即执行async函数包裹递归调用的写法是错误的:
(async () => await NN_walkd(...))()
这种写法会创建一个未被等待的Promise,导致:
- 主函数
NN_walk中的await NN_walkd无法等待所有递归分支完成,就直接执行req.session.walk = null和cb() - 递归产生的Promise没有被捕获,一旦出错会触发未处理的Promise拒绝
正确写法:直接用await调用NN_walkd,因为它本身就是async函数,返回Promise:
// 替换原递归调用代码 await NN_walkd(req, [sObj.COMPANYKEY,int1], cnode, Array.from(chain), tlvl);
2. 解决compSearchK卡住的问题
首先修复查询函数的反模式
你的compSearchK和compSearchM都用了**new Promise嵌套async函数**的反模式,这完全没必要,async函数本身就返回Promise,嵌套写法会打乱异步流程,是卡住的潜在原因。
修复后的代码:
// 修复compSearchK async compSearchK(ckey) { const sckey = await this._companyRepo.search() .where('COMPANYKEY').equals(ckey) .return.all(); if (!sckey.length) return null; const ttrr = await this._companyRepo.fetch(sckey[0].entityId); return ttrr.toJSON(); } // 修复compSearchM(用Promise.all并行优化效率) async compSearchM(mkey) { const smkey = await this._companyRepo.search() .where('MASTERKEY').equals(mkey) .and('TBLNUM').equals(10) .return.all(); if (!smkey.length) return []; return Promise.all( smkey.map(item => this._companyRepo.fetch(item.entityId).then(ttrr => ttrr.toJSON())) ); }
修复递归遍历的异步等待
原代码中用for...in遍历数组(这本身就不合适,for...in是遍历对象属性的),且没有等待所有递归调用完成,会导致大量未处理的Promise同时发起,可能耗尽Redis连接池或阻塞请求队列,进而卡住.search调用。
修改walk.js中的else分支:
else { const sArr = await req.app.get('redis').compSearchM(cnode[0]); if (sArr.length) { // 用Promise.all等待所有递归分支完成,同时用map遍历数组 await Promise.all( sArr.map(item => { return NN_walkd(req, [item.COMPANYKEY, item.CLIENTKEY], cnode, Array.from(chain), tlvl); }) ); } else { console.log('no more links on this chain: ',cnode); } }
额外注意
- 遍历数组优先用
for...of或Array.map,避免用for...in - 递归遍历循环链时要注意检测循环,避免无限递归
内容的提问来源于stack exchange,提问作者ImTalkingCode
相关产品推荐
相关产品推荐

