#json #angular #electron #ipcrenderer
#json #angular #электрон #ipcrenderer
Вопрос:
У меня возникла странная проблема с проектами Angular 7.1.1 и Electron 4.1.4.
Поток данных:
- Угловой компонент «Построитель отчетов» собирает параметры конфигурации отчета из проверенной формы FormGroup и FormControl и отправляет данные в docx-templater.service
- Кнопка пользователя запускает функцию createReport()
- При отправке параметров для полного отчета функция createReport() вызывает функцию fnGetCompleteControlList() DataService, которая асинхронно возвращает правильно настроенный JSON.
- с помощью функции .then() после асинхронного извлечения данных функция createReport() объединяет выходной каталог, который является частью формы конфигурации, и отправляет оба в функцию createCompleteDocument() docx-templater.service. Как только обещание возвращается, оно обновляет пользовательский интерфейс.
- Функция createCompleteDocument службы Angular «docx-templater» передает значения данных и папок в ipcRenderer.send для электронного канала «writeCompleteDocument» и возвращает обещание.
- В моем main.ts у меня есть ipcMain.on для канала «writeCompleteDocument», который передает данные в функцию write-docx для обработки этих данных в документе word.
Проблема: когда данные попадают в мою функцию write-docx, в ней отсутствует подмассив объектов, которые необходимы для процесса экспорта.
Я проверил, что данные в консоли инструментов разработчика Chrome в electron в данный момент идеальны, непосредственно перед отправкой данных в docx-templater.service и непосредственно перед тем, как эта служба отправит их в ipcRenderer (это означает, что мои функции службы данных и построения отчетов работают так, как задумано). Когда я проверяю данные в main.ts, сохраняя данные в файле JSON, в нем отсутствует подмассив controls только во втором объекте 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 передаст JSON точно так, как он есть, в функцию ipcMain.on, но каким-то образом он обрезает часть данных. Я даже пытался упорядочить данные перед отправкой их в средство визуализации, а затем проанализировать их на другой стороне, но это ничего не дало.
Может ли это быть асинхронным? Я не знаю, куда идти дальше, чтобы отладить и найти, какую идиотскую ошибку я допустил в процессе.
Кроме того, я понимаю, что приведенный выше поток данных кажется чрезмерно сложным для того, что я делаю, и что я, вероятно, могу сделать это проще, но это имеет смысл (своего рода) для того, как структурировано все приложение, поэтому я собираюсь пойти с ним, если смогу устранить эту ошибку.
Ответ №1:
Похоже, ваша createCompleteDocument()
функция настроена неправильно. Быстрый поиск показал мне, что ipcRenderer
это асинхронная функция, но вы отвечаете на нее (почти) синхронно.
У вас есть следующее, что (вероятно) неверно (на самом деле это определенно неверно, потому что вы ввели тип возврата, как Promise<boolean>
когда он есть 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()
and 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
}
}
Ответ №2:
Я смог решить эту проблему, добавив тайм-аут в 1000 мс после извлечения данных fnGetCompleteControlList() в report-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
}