当从 Angular 7 服务传递时,ipcRenderer 参数 JSON 对象如何丢失数据?

How is the ipcRenderer argument JSON object losing data when passed from an Angular 7 service?

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

数据流:

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

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

我已经验证数据在电子的 Chrome 开发者工具控制台中是完美的,就在它发送数据到 docx-templater.service 之前和那个服务发送它之前到 ipcRenderer(意味着我的数据服务和 Report Builder 功能按设计工作)。当我通过将数据保存到 JSON 文件来检查 main.ts 中的数据时,它仅缺少 JSON 的第二个对象中的控件子数组。控件子数组按预期显示在第一个对象中。

我会注意到 ipcMain 函数输出的是一个格式正确的 JSON 文件,因此它实际上只是排除了 "controls" 子数组,并且不会因内存或缓冲区而被截断限制或类似的东西。

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 会像传递给 ipcMain.on 函数一样传递 JSON,但不知何故它正在修剪部分数据。我什至尝试过在将数据发送到渲染器之前对数据进行 strigifying,然后在另一端解析它,但是什么也没做。

这可能是一个异步的东西吗?我不知道下一步该去哪里调试,也不知道我在这个过程中犯了什么白痴错误。

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

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

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
    }

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

您有以下内容(可能)不正确(实际上它绝对不正确,因为您将 return 键入为 Promise<boolean> 而它是 Promise<void>):

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

ipcRenderer#send() is async but you are calling resolve() immediately afterwards without waiting for the function to resolve. This probably explains why adding the setTimeout() is fixing the problem for you. Looking at the ipcRenderer docs,以下可能会满足您的要求:

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

看起来回调传递了 Event object

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

Sending a synchronous message will block the whole renderer process, unless you know what you are doing you should never use it.

利用 ipcRenderer#send()ipcRenderer#once() 几乎绝对是正确的选择。

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

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
  }
}