Преглед изворни кода

refactor: Replace frontend Promise.all with forEachIn

Owen Diffey пре 1 година
родитељ
комит
975029c54c

+ 22 - 22
frontend/src/Model.ts

@@ -1,3 +1,4 @@
+import { forEachIn } from "@common/utils/forEachIn";
 import { useModelStore } from "./stores/model";
 import { useWebsocketStore } from "./stores/websocket";
 
@@ -34,17 +35,15 @@ export default class Model {
 
 				if (typeof value === "object" && value._id) paths.push(path);
 				else if (Array.isArray(value))
-					await Promise.all(
-						value.map(async item => {
-							if (typeof item !== "object") return;
-
-							if (item._id) paths.push(path);
-							else
-								paths.push(
-									...(await this._getRelations(item, path))
-								);
-						})
-					);
+					await forEachIn(value, async item => {
+						if (typeof item !== "object") return;
+
+						if (item._id) paths.push(path);
+						else
+							paths.push(
+								...(await this._getRelations(item, path))
+							);
+					});
 				else paths.push(...(await this._getRelations(value, path)));
 
 				return paths;
@@ -87,8 +86,9 @@ export default class Model {
 		pathParts.push(head);
 
 		if (Array.isArray(model[head])) {
-			await Promise.all(
-				model[head].map(async (item, index) => {
+			await forEachIn(
+				model[head],
+				async (item, index) => {
 					let itemPath = `${index}`;
 
 					if (rest.length > 0) itemPath += `.${rest.join(".")}`;
@@ -96,7 +96,8 @@ export default class Model {
 					await this._loadRelation(model[head], itemPath, force, [
 						...pathParts
 					]);
-				})
+				},
+				{ concurrency: 1 }
 			);
 
 			return;
@@ -139,20 +140,19 @@ export default class Model {
 		if (relations)
 			relations = Array.isArray(relations) ? relations : [relations];
 
-		await Promise.all(
-			(relations ?? []).map(async path => {
-				await this._loadRelation(this, path, force);
-			})
-		);
+		await forEachIn(relations ?? [], async path => {
+			await this._loadRelation(this, path, force);
+		});
 	}
 
 	public async unregisterRelations(): Promise<void> {
 		const { unregisterModels } = useModelStore();
-		const relationIds = await Promise.all(
-			this._loadedRelations.map(async path => {
+		const relationIds = await forEachIn(
+			this._loadedRelations,
+			async path => {
 				const relation = await this._getRelation(path);
 				return relation._id;
-			})
+			}
 		);
 		await unregisterModels(relationIds);
 	}

+ 20 - 23
frontend/src/components/AdvancedTable.vue

@@ -14,6 +14,7 @@ import { useRoute, useRouter } from "vue-router";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { DraggableList } from "vue-draggable-list";
+import { forEachIn } from "@common/utils/forEachIn";
 import { useWebsocketStore } from "@/stores/websocket";
 import { useModalsStore } from "@/stores/modals";
 import keyboardShortcuts from "@/keyboardShortcuts";
@@ -235,11 +236,9 @@ const unsubscribe = async (_subscriptions?) => {
 		])
 	);
 
-	await Promise.all(
-		Object.keys(_subscriptions).map(async modelId => {
-			delete subscriptions.value[modelId];
-		})
-	);
+	await forEachIn(Object.keys(_subscriptions), async modelId => {
+		delete subscriptions.value[modelId];
+	});
 };
 
 const subscribe = async () => {
@@ -247,12 +246,11 @@ const subscribe = async () => {
 		JSON.stringify(subscriptions.value)
 	);
 
-	await Promise.all(
-		rows.value
-			.filter(row => subscriptions.value[row._id])
-			.map(async row => {
-				delete previousSubscriptions[row._id];
-			})
+	await forEachIn(
+		rows.value.filter(row => subscriptions.value[row._id]),
+		async row => {
+			delete previousSubscriptions[row._id];
+		}
 	);
 
 	const uuids = await events.subscribeMany(
@@ -272,16 +270,13 @@ const subscribe = async () => {
 		)
 	);
 
-	await Promise.all(
-		Object.entries(uuids).map(async ([channel, uuid]) => {
-			const [, , , event, modelId] =
-				/^([a-z]+)\.([a-z]+)\.([A-z]+)\.?([A-z0-9]+)?$/.exec(channel) ??
-				[];
+	await forEachIn(Object.entries(uuids), async ([channel, uuid]) => {
+		const [, , , event, modelId] =
+			/^([a-z]+)\.([a-z]+)\.([A-z]+)\.?([A-z0-9]+)?$/.exec(channel) ?? [];
 
-			subscriptions.value[modelId] ??= {};
-			subscriptions.value[modelId][event] = uuid;
-		})
-	);
+		subscriptions.value[modelId] ??= {};
+		subscriptions.value[modelId][event] = uuid;
+	});
 
 	unsubscribe(previousSubscriptions);
 };
@@ -975,15 +970,17 @@ onMounted(async () => {
 
 		if (props.query) setQuery();
 
-		await Promise.allSettled(
-			props.filters.map(async filter => {
+		await forEachIn(
+			props.filters,
+			async filter => {
 				if (filter.autosuggest && filter.autosuggestDataAction) {
 					const { items } = await runJob(
 						filter.autosuggestDataAction
 					);
 					autosuggest.value.allItems[filter.name] = items;
 				}
-			})
+			},
+			{ onError: Promise.resolve }
 		);
 	});
 

+ 17 - 17
frontend/src/composables/useEvents.ts

@@ -1,4 +1,5 @@
 import { onBeforeUnmount, ref } from "vue";
+import { forEachIn } from "@common/utils/forEachIn";
 import { useWebsocketStore } from "@/stores/websocket";
 
 export const useEvents = () => {
@@ -34,12 +35,11 @@ export const useEvents = () => {
 	const subscribeMany = async channels => {
 		const _subscriptions = await websocketStore.subscribeMany(channels);
 
-		await Promise.all(
-			Object.entries(_subscriptions).map(
-				async ([uuid, { channel, callback }]) => {
-					subscriptions.value[uuid] = { channel, callback };
-				}
-			)
+		await forEachIn(
+			Object.entries(_subscriptions),
+			async ([uuid, { channel, callback }]) => {
+				subscriptions.value[uuid] = { channel, callback };
+			}
 		);
 
 		return Object.fromEntries(
@@ -69,22 +69,22 @@ export const useEvents = () => {
 
 		await websocketStore.unsubscribeMany(_subscriptions);
 
-		return Promise.all(
-			uuids.map(async uuid => {
-				delete subscriptions.value[uuid];
-			})
-		);
+		return forEachIn(uuids, async uuid => {
+			delete subscriptions.value[uuid];
+		});
 	};
 
 	onBeforeUnmount(async () => {
-		await Promise.allSettled(
-			Object.keys(subscriptions.value).map(uuid => unsubscribe(uuid))
+		await forEachIn(
+			Object.keys(subscriptions.value),
+			uuid => unsubscribe(uuid),
+			{ onError: Promise.resolve }
 		);
 
-		await Promise.allSettled(
-			Object.keys(readySubscriptions.value).map(async uuid =>
-				removeReadyCallback(uuid)
-			)
+		await forEachIn(
+			Object.keys(readySubscriptions.value),
+			async uuid => removeReadyCallback(uuid),
+			{ onError: Promise.resolve }
 		);
 	});
 

+ 31 - 33
frontend/src/composables/useModels.ts

@@ -1,4 +1,5 @@
 import { onBeforeUnmount, ref } from "vue";
+import { forEachIn } from "@common/utils/forEachIn";
 import { useModelStore } from "@/stores/model";
 
 export const useModels = () => {
@@ -67,27 +68,26 @@ export const useModels = () => {
 	};
 
 	const setupDeletedSubscriptions = (registeredModels: any[]) =>
-		Promise.all(
-			registeredModels
-				.filter(
-					(model, index) =>
-						!deletedSubscriptions.value[model._name] &&
-						registeredModels.findIndex(
-							storeModel => storeModel._name === model._name
-						) === index
-				)
-				.map(async registeredModel => {
-					deletedSubscriptions.value[registeredModel._name] =
-						await onDeleted(registeredModel._name, ({ oldDoc }) => {
-							const modelIndex = models.value.findIndex(
-								model => model._id === oldDoc._id
-							);
-
-							if (modelIndex < 0) return;
-
-							delete models.value[modelIndex];
-						});
-				})
+		forEachIn(
+			registeredModels.filter(
+				(model, index) =>
+					!deletedSubscriptions.value[model._name] &&
+					registeredModels.findIndex(
+						storeModel => storeModel._name === model._name
+					) === index
+			),
+			async registeredModel => {
+				deletedSubscriptions.value[registeredModel._name] =
+					await onDeleted(registeredModel._name, ({ oldDoc }) => {
+						const modelIndex = models.value.findIndex(
+							model => model._id === oldDoc._id
+						);
+
+						if (modelIndex < 0) return;
+
+						delete models.value[modelIndex];
+					});
+			}
 		);
 
 	const registerModels = async (
@@ -111,11 +111,11 @@ export const useModels = () => {
 		modelIds: string | string[],
 		relations?: Record<string, string | string[]>
 	) => {
-		const registeredModels = await modelStore.loadModels(
-			modelName,
-			modelIds,
-			relations
-		);
+		const registeredModels = (
+			await modelStore.loadModels(modelName, modelIds, relations)
+		).filter(model => model !== null);
+
+		if (registerModels.length === 0) return registeredModels;
 
 		models.value.push(...registeredModels);
 
@@ -137,16 +137,14 @@ export const useModels = () => {
 	};
 
 	onBeforeUnmount(async () => {
-		await Promise.all(
-			Object.entries(subscriptions.value).map(async ([type, uuids]) =>
+		await forEachIn(
+			Object.entries(subscriptions.value),
+			async ([type, uuids]) =>
 				Object.entries(uuids).map(async ([modelName, _subscriptions]) =>
-					Promise.all(
-						_subscriptions.map(uuid =>
-							removeCallback(modelName, type, uuid)
-						)
+					forEachIn(_subscriptions, uuid =>
+						removeCallback(modelName, type, uuid)
 					)
 				)
-			)
 		);
 		await unregisterModels(models.value.map(model => model._id));
 	});

+ 56 - 63
frontend/src/stores/model.ts

@@ -1,6 +1,7 @@
 import { reactive, ref, computed } from "vue";
 import { defineStore } from "pinia";
 import { generateUuid } from "@common/utils/generateUuid";
+import { forEachIn } from "@common/utils/forEachIn";
 import { useWebsocketStore } from "./websocket";
 import Model from "@/Model";
 
@@ -38,50 +39,46 @@ export const useModelStore = defineStore("model", () => {
 	};
 
 	const unregisterModels = async modelIds =>
-		Promise.all(
-			(Array.isArray(modelIds) ? modelIds : [modelIds]).map(
-				async modelId => {
-					const model = models.value.find(
-						model => model._id === modelId
-					);
-
-					if (!model || model.getUses() > 1) {
-						model?.removeUse();
+		forEachIn(
+			Array.isArray(modelIds) ? modelIds : [modelIds],
+			async modelId => {
+				const model = models.value.find(model => model._id === modelId);
 
-						return;
-					}
+				if (!model || model.getUses() > 1) {
+					model?.removeUse();
 
-					await model.unregisterRelations();
+					return;
+				}
 
-					const { updated, deleted } = model.getSubscriptions() ?? {};
+				await model.unregisterRelations();
 
-					if (updated)
-						await unsubscribe(
-							`model.${model.getName()}.updated.${modelId}`,
-							updated
-						);
+				const { updated, deleted } = model.getSubscriptions() ?? {};
 
-					if (deleted)
-						await unsubscribe(
-							`model.${model.getName()}.deleted.${modelId}`,
-							deleted
-						);
+				if (updated)
+					await unsubscribe(
+						`model.${model.getName()}.updated.${modelId}`,
+						updated
+					);
 
-					models.value.splice(
-						models.value.findIndex(model => model._id === modelId),
-						1
+				if (deleted)
+					await unsubscribe(
+						`model.${model.getName()}.deleted.${modelId}`,
+						deleted
 					);
-				}
-			)
+
+				models.value.splice(
+					models.value.findIndex(model => model._id === modelId),
+					1
+				);
+			}
 		);
 
 	const onCreatedCallback = async (modelName: string, data) => {
 		if (!subscriptions.value.created[modelName]) return;
 
-		await Promise.all(
-			Object.values(subscriptions.value.created[modelName]).map(
-				async subscription => subscription(data) // TODO: Error handling
-			)
+		await forEachIn(
+			Object.values(subscriptions.value.created[modelName]),
+			async subscription => subscription(data) // TODO: Error handling
 		);
 	};
 
@@ -121,10 +118,9 @@ export const useModelStore = defineStore("model", () => {
 
 		if (!subscriptions.value.updated[modelName]) return;
 
-		await Promise.all(
-			Object.values(subscriptions.value.updated[modelName]).map(
-				async subscription => subscription(data) // TODO: Error handling
-			)
+		await forEachIn(
+			Object.values(subscriptions.value.updated[modelName]),
+			async subscription => subscription(data) // TODO: Error handling
 		);
 	};
 
@@ -144,10 +140,9 @@ export const useModelStore = defineStore("model", () => {
 		const { oldDoc } = data;
 
 		if (subscriptions.value.deleted[modelName])
-			await Promise.all(
-				Object.values(subscriptions.value.deleted[modelName]).map(
-					async subscription => subscription(data) // TODO: Error handling
-				)
+			await forEachIn(
+				Object.values(subscriptions.value.deleted[modelName]),
+				async subscription => subscription(data) // TODO: Error handling
 			);
 
 		const index = models.value.findIndex(model => model._id === oldDoc._id);
@@ -184,38 +179,36 @@ export const useModelStore = defineStore("model", () => {
 		docs,
 		relations?: Record<string, string | string[]>
 	) =>
-		Promise.all(
-			(Array.isArray(docs) ? docs : [docs]).map(async _doc => {
-				const existingRef = models.value.find(
-					model => model._id === _doc._id
-				);
+		forEachIn(Array.isArray(docs) ? docs : [docs], async _doc => {
+			const existingRef = models.value.find(
+				model => model._id === _doc._id
+			);
 
-				const docRef = existingRef ?? reactive(new Model(_doc));
+			const docRef = existingRef ?? reactive(new Model(_doc));
 
-				docRef.addUse();
+			docRef.addUse();
 
-				if (existingRef) return docRef;
+			if (existingRef) return docRef;
 
-				if (relations && relations[docRef._name])
-					await docRef.loadRelations(relations[docRef._name]);
+			if (relations && relations[docRef._name])
+				await docRef.loadRelations(relations[docRef._name]);
 
-				models.value.push(docRef);
+			models.value.push(docRef);
 
-				const updatedUuid = await subscribe(
-					`model.${docRef._name}.updated.${_doc._id}`,
-					data => onUpdatedCallback(docRef._name, data)
-				);
+			const updatedUuid = await subscribe(
+				`model.${docRef._name}.updated.${_doc._id}`,
+				data => onUpdatedCallback(docRef._name, data)
+			);
 
-				const deletedUuid = await subscribe(
-					`model.${docRef._name}.deleted.${_doc._id}`,
-					data => onDeletedCallback(docRef._name, data)
-				);
+			const deletedUuid = await subscribe(
+				`model.${docRef._name}.deleted.${_doc._id}`,
+				data => onDeletedCallback(docRef._name, data)
+			);
 
-				docRef.setSubscriptions(updatedUuid, deletedUuid);
+			docRef.setSubscriptions(updatedUuid, deletedUuid);
 
-				return docRef;
-			})
-		);
+			return docRef;
+		});
 
 	const findById = async (modelName: string, _id) => {
 		const existingModel = models.value.find(model => model._id === _id);

+ 27 - 29
frontend/src/stores/websocket.ts

@@ -1,6 +1,7 @@
 import { defineStore } from "pinia";
 import { ref } from "vue";
 import { generateUuid } from "@common/utils/generateUuid";
+import { forEachIn } from "@common/utils/forEachIn";
 import { useConfigStore } from "./config";
 import { useUserAuthStore } from "./userAuth";
 import ms from "@/ms";
@@ -136,23 +137,20 @@ export const useWebsocketStore = defineStore("websocket", () => {
 			delete subscriptions.value[channel];
 		});
 
-		return Promise.all(
-			Object.entries(channels).map(async ([uuid, channel]) => {
-				if (
-					!subscriptions.value[channel] ||
-					!subscriptions.value[channel].callbacks[uuid]
-				)
-					return;
-
-				delete subscriptions.value[channel].callbacks[uuid];
-
-				if (
-					Object.keys(subscriptions.value[channel].callbacks)
-						.length === 0
-				)
-					delete subscriptions.value[channel];
-			})
-		);
+		return forEachIn(Object.entries(channels), async ([uuid, channel]) => {
+			if (
+				!subscriptions.value[channel] ||
+				!subscriptions.value[channel].callbacks[uuid]
+			)
+				return;
+
+			delete subscriptions.value[channel].callbacks[uuid];
+
+			if (
+				Object.keys(subscriptions.value[channel].callbacks).length === 0
+			)
+				delete subscriptions.value[channel];
+		});
 	};
 
 	const unsubscribeAll = async () => {
@@ -194,16 +192,17 @@ export const useWebsocketStore = defineStore("websocket", () => {
 
 		await userAuthStore.updatePermissions();
 
-		await Promise.all(
-			Object.values(readyCallbacks.value).map(
-				async callback => callback() // TODO: Error handling
-			)
+		await forEachIn(
+			Object.values(readyCallbacks.value),
+			async callback => callback() // TODO: Error handling
 		);
 
-		await Promise.allSettled(
-			Object.keys(subscriptions.value)
-				.filter(channel => !socketChannels.includes(channel))
-				.map(channel => runJob("events.subscribe", { channel }))
+		await forEachIn(
+			Object.keys(subscriptions.value).filter(
+				channel => !socketChannels.includes(channel)
+			),
+			channel => runJob("events.subscribe", { channel }),
+			{ onError: Promise.resolve }
 		);
 
 		pendingJobs.value.forEach(message => socket.value.send(message));
@@ -230,10 +229,9 @@ export const useWebsocketStore = defineStore("websocket", () => {
 
 		if (!subscriptions.value[name]) return;
 
-		await Promise.all(
-			Object.values(subscriptions.value[name].callbacks).map(
-				async subscription => subscription(...data) // TODO: Error handling
-			)
+		await forEachIn(
+			Object.values(subscriptions.value[name].callbacks),
+			async subscription => subscription(...data) // TODO: Error handling
 		);
 	};