Angular5中Object.keys无法按预期使用的问题排查
核心问题:异步代码的时序坑!
兄弟,你遇到的Object.keys返回空数组的问题,本质是异步操作的执行顺序搞反了。咱们一点点理清楚:
你的tabContentSetup函数里,this.gitApiService.getPrs(repo)是个Observable,订阅它的回调函数是异步执行的——也就是说,当你调用return Object.keys(filteredObject)的时候,所有的PR请求还没完成,回调里填充filteredObject的代码根本没跑,那时候filteredObject就是个空对象,Object.keys自然返回空数组。至于你console.log看到对象正常,那是浏览器控制台的小把戏:它会动态更新对象的引用,你展开的时候回调已经执行完填充数据了,但实际代码执行到return的时候,对象还是空的。
符合Angular规范的解决方案:用RxJS协调异步流
在Angular里处理多个异步请求,RxJS是标准工具,咱们用forkJoin来等待所有仓库的PR请求完成,再统一处理数据,这样就能确保filteredObject被完全填充后再去拿它的键名。
重构后的代码示例
先把单个仓库的PR处理逻辑抽成独立函数,让代码更清晰:
import { forkJoin } from 'rxjs'; import { map } from 'rxjs/operators'; // 抽离单个仓库的PR处理逻辑 private processRepoPRs(repo: string) { return this.gitApiService.getPrs(repo).pipe( map(prObjectArray => { // 先过滤掉讲师提交的PR const validPRs = prObjectArray.filter(pr => !this.instructorsArray.includes(pr.user.login.toLowerCase()) ); // 用reduce聚合数据,替代嵌套的forEach return validPRs.reduce((repoData, pr) => { const prUser = pr.user.login.toLowerCase(); const cohortInfo = this.gitHubDict[prUser]; if (!cohortInfo) { console.warn(`Cannot find: ${prUser} in Cohort JSON files`); return repoData; } const assignedCohort = cohortInfo.cohort; // 初始化仓库数据(如果不存在) if (!repoData[repo]) { repoData[repo] = { count: 0, cohortsArray: [], cohorts: {} }; } const currentRepo = repoData[repo]; currentRepo.count++; // 初始化 cohort 数据(如果不存在) if (!currentRepo.cohorts[assignedCohort]) { currentRepo.cohortsArray.push(assignedCohort); currentRepo.cohorts[assignedCohort] = { count: 0, prs: [] }; } const currentCohort = currentRepo.cohorts[assignedCohort]; currentCohort.count++; currentCohort.prs.push({ name: `${cohortInfo.first} ${cohortInfo.last}`, cohort: assignedCohort, git: prUser, url: pr.html_url, created_at: pr.created_at, updated_at: pr.updated_at }); return repoData; }, {} as Record<string, any>); }) ); } tabContentSetup(tabArray: string[]) { // 为每个仓库生成对应的处理Observable const repoObservables = tabArray.map(repo => this.processRepoPRs(repo)); // 等待所有仓库的PR处理完成,合并结果 return forkJoin(repoObservables).pipe( map(repoResults => { // 把多个仓库的结果合并成一个对象 const finalFilteredObject = repoResults.reduce((acc, result) => ({ ...acc, ...result }), {}); // 现在可以正常获取键名了 return { filteredObject: finalFilteredObject, repoKeys: Object.keys(finalFilteredObject) }; }) ); }
在组件中正确使用的方式
在ngOnInit里订阅这个函数返回的Observable,而不是同步调用:
ngOnInit() { this.tabContentSetup(yourTabArray).subscribe(result => { this.filteredObject = result.filteredObject; this.repoKeys = result.repoKeys; // 这里可以放心用repoKeys做*ngFor遍历了 }); }
额外的代码优化点
- 用
reduce替代嵌套的forEach和条件判断,让数据聚合逻辑更简洁易读; - 抽离独立的处理函数,方便后续维护和单元测试;
- 完全遵循Angular的异步处理规范,避免回调地狱和时序问题。
内容的提问来源于stack exchange,提问作者Rudenate3
相关产品推荐
相关产品推荐

