0

我在 Angular 7.1.1 和 Electron 4.1.4 项目中遇到了一个奇怪的问题。

数据流:

  1. Angular 组件“报表生成器”从经过 FormGroup 和 FormControl 验证的表单收集报表配置选项,并将数据发送到 docx-templater.service
    • 用户按钮触发 createReport() 函数
    • 当提交完整报告的选项时,createReport() 函数调用 dataService 的 fnGetCompleteControlList() 异步返回正确配置的 JSON。
    • 在异步数据检索后使用 .then() 函数,createReport() 函数将作为配置表单一部分的输出目录组合起来,并将两者发送到 docx-templater.service 的 createCompleteDocument() 函数。一旦 promise 返回,它就会更新 UI。
  2. Angular 服务“docx-templater”的 createCompleteDocument 函数将数据和文件夹值传递给电子“writeCompleteDocument”通道的 ipcRenderer.send 并返回一个承诺。
  3. 在我的 main.ts 中,我有一个用于“writeCompleteDocument”通道的 ipcMain.on,它将数据传递给 write-docx 函数,以将该数据处理为 word 文档。

问题: 当数据到达我的 write-docx 函数时,它缺少一个对导出过程至关重要的对象子数组。

在将数据发送到 docx-templater.service 之前以及在该服务将其发送到 ipcRenderer 之前(即我的数据服务和报告生成器),我已经验证了电子的 Chrome 开发人员工具控制台中的数据是完美的功能按设计工作)。当我通过将数据保存到 JSON 文件来检查 main.ts 中的数据时,它仅缺少 JSON 的第二个对象中的控件子数组。控件子数组按预期显示在第一个对象中。

我会注意到,从 ipcMain 函数出来的是一个格式正确的 JSON 文件,所以它实际上只是排除了“控件”子数组,并且由于内存或缓冲区限制或类似的东西而没有截断。

report-builder.component.ts

createReport() {
    if (this.reportBuilderFG.get('allControls').value) {
      this.db.fnGetCompleteControlList()
        .then((groups: Group[]) => {
          this.word.createCompleteDocument(groups, this.reportBuilderFG.get('folder').value + '\\filename.docx')
          .then(() => {
            this.openSnackBar(this.reportBuilderFG.get('folder').value + '\\filename.docx created successfully');
          });
        });
    } else {
      // Do other stuff
    }
docx-templater.service.ts

createCompleteDocument(data, folder: string): Promise<boolean> {
    return new Promise(resolve => {
      console.log(data) <=== Data is perfect here.
      ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
      resolve();
    });
  }

main.ts
import { writeCompleteDocument } from './node_scripts/write-docx';

ipcMain.on('writeCompleteDocument', (event, arg) => {
  fs.writeFileSync("IPCdata.json", arg.data); // <==== Part of the data is missing here.
  writeCompleteDocument(arg.data, arg.folder);
});

Good Data Example (some keys and objects excluded for brevity)
[
  {
    "name": "General Security",
    "order": 1,
    "subgroups": [
      {
        "_id": "GOV",
        "name": "Governance",
        "order": 1,
        "controls": [
          {
            "group": "GS",
            "subgroup": "GOV",
            "active": true,
            "printOrder": 1,
            "name": "This is my GS control name",
            "requirements": [
              {
                "id": "SA01",
                "active": true,
                "order": 1,
                "type": "SA",
                "applicability": [
                  "ABC",
                  "DEF",
                  "GHI"
                ],
              },
              { ... 3 more  }
            ],
            "_id": "GSRA-03",
            "_rev": "1-0cbdefc93e56683bc98bae3a122f9783"
          },
          { ... 3 more }
    ],
    "_id": "GS",
    "_rev": "1-b94d1651589eefd5ef0a52360dac6f9d"
  },
  {
    "order": 2,
    "name": "IT Security",
    "subgroups": [
      {
        "_id": "PLCY",
        "order": 1,
        "name": "Policies",
        "controls": [ <==== This entire sub array is missing when exporting from IPC Main
          {
            "group": "IT",
            "subgroup": "PLCY",
            "active": true,
            "printOrder": 1,
            "name": "This is my IT control name",
            "requirements": [
              {
                "id": "SA01",
                "active": true,
                "order": 1,
                "type": "SA",
                "applicability": [
                  "ABC",
                  "DEF",
                  "GHI"
                ],
              }
            ],
            "_id": "GSRA-03",
            "_rev": "1-0cbdefc93e56683bc98bae3a122f9783"
          }
      }
    ],
    "_id": "IT",
    "_rev": "2-e6ff53456e85b45d9bafd791652a945c"
  }
]

我本来希望 ipcRenderer 将 JSON 完全按原样传递给 ipcMain.on 函数,但不知何故它正在修剪部分数据。我什至尝试在将数据发送到渲染器之前对数据进行分类,然后在另一端解析它,但这没有任何作用。

这可能是异步的吗?我不知道下一步该去哪里调试并找出我在此过程中犯的白痴错误。

另外,我意识到上面的数据流对于我正在做的事情来说似乎过于复杂,而且我可能可以更容易地做到这一点,但是对于整个应用程序的结构方式来说它是有意义的(有点),所以我将继续使用它如果我能解决这个错误。

4

2 回答 2

0

在我的 fnGetCompleteControlList() 数据拉入 report-builder.component.ts 之后,我可以通过添加 1000 毫秒的超时来解决这个问题。看来我在学习异步函数方面还有很多工作要做。:-(

report-builder.component.ts

createReport() {
    if (this.reportBuilderFG.get('allControls').value) {
      this.db.fnGetCompleteControlList()
        .then((groups: Group[]) => {
          setTimeout(() => {
              this.word.createCompleteDocument(groups, this.reportBuilderFG.get('folder').value + '\\filename.docx')
              .then(() => {
                  this.openSnackBar(this.reportBuilderFG.get('folder').value + '\\filename.docx created successfully');
              });
          }, 1000);
        });
    } else {
      // Do other stuff
    }
于 2019-04-21T19:34:47.497 回答
0

看起来您的createCompleteDocument()功能设置不正确。快速搜索显示这ipcRenderer是一个异步函数,但您(几乎)同步响应它。

您有以下内容,这(可能)是不正确的(实际上它绝对不正确,因为您输入的 return as Promise<boolean>when it is Promise<void>):

createCompleteDocument(data, folder: string): Promise<boolean> {
  return new Promise(resolve => {
    ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
    resolve();
  });
}

ipcRenderer#send()是异步的,但您resolve()随后立即调用,而无需等待函数解析。这可能解释了为什么添加setTimeout()可以为您解决问题。查看ipcRenderer文档,以下内容可能符合您的要求:

createCompleteDocument(data, folder: string): Promise<Event> {
  return new Promise(resolve => {
    ipcRenderer.once('writeCompleteDocument', resolve);
    ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
  });
}

看起来回调传递了一个事件对象

另一种选择是在原始代码中简单地替换ipcRenderer#send()ipcRenderer#sendSync(),但正如该方法的文档中所指出的那样:

发送同步消息将阻塞整个渲染器进程,除非您知道自己在做什么,否则永远不要使用它。

利用ipcRenderer#send()ipcRenderer#once()几乎肯定是要走的路。

另外,您可以通过切换到async/await 函数来清理代码。例如:

async createReport(): Promise<void> {
  if (this.reportBuilderFG.get('allControls').value) {
    const groups: Group[] = await this.db.fnGetCompleteControlList();

    await this.word.createCompleteDocument(
      groups,
      this.reportBuilderFG.get('folder').value + '\\filename.docx'
    );

    // Unclear if this function is actually async 
    await this.openSnackBar(
      this.reportBuilderFG.get('folder').value +
        '\\filename.docx created successfully'
    );
  } else {
    // Do other stuff
  }
}
于 2019-04-22T14:31:09.040 回答