diff --git a/src/Projects.tsx b/src/Projects.tsx
index 9d6fdc6..d278687 100644
--- a/src/Projects.tsx
+++ b/src/Projects.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { ProjectData, SERVER } from "./api";
+import { ProjectData, ProjectsRequestTimeoutError, SERVER } from "./api";
export interface NameById {
[key: number]: string;
@@ -22,7 +22,11 @@ export default function Projects({ selectedUser, nameById }: ProjectsProps) {
setProjects(projects => [...(projects ?? []), ...page.projects]);
}
setHasMoreResults(page.hasMoreResults);
- }).catch(() => {
+ }).catch((error) => {
+ if (error instanceof ProjectsRequestTimeoutError) {
+ alert(error.message);
+ return;
+ }
alert("Something went wrong...");
});
}, [selectedUser]);
@@ -47,4 +51,4 @@ export default function Projects({ selectedUser, nameById }: ProjectsProps) {
);
-}
\ No newline at end of file
+}
diff --git a/src/api/apiImpl.ts b/src/api/apiImpl.ts
index 41a1a55..6e172df 100644
--- a/src/api/apiImpl.ts
+++ b/src/api/apiImpl.ts
@@ -5,6 +5,27 @@ export interface ProjectsResponse {
hasMoreResults: boolean;
}
+const PROJECTS_REQUEST_TIMEOUT_MS = 1000;
+
+export const PROJECTS_REQUEST_TIMEOUT_MESSAGE =
+ "Request timed out. Possible infinite loop in api/pagination.py.";
+
+export class ProjectsRequestTimeoutError extends Error {
+ constructor(message: string = PROJECTS_REQUEST_TIMEOUT_MESSAGE) {
+ super(message);
+ this.name = "ProjectsRequestTimeoutError";
+ }
+}
+
+function isAbortError(error: unknown): boolean {
+ return (
+ typeof error === "object" &&
+ error !== null &&
+ "name" in error &&
+ error.name === "AbortError"
+ );
+}
+
class DefaultServer {
async getUsers(): Promise {
const response = await fetch('http://127.0.0.1:5000/api/users');
@@ -28,8 +49,20 @@ class DefaultServer {
url.searchParams.append('pageSize', options.pageSize.toString());
}
- const response = await fetch(url);
- return response.json();
+ const controller = new AbortController();
+ const timeoutId = window.setTimeout(() => controller.abort(), PROJECTS_REQUEST_TIMEOUT_MS);
+
+ try {
+ const response = await fetch(url, { signal: controller.signal });
+ return response.json();
+ } catch (error) {
+ if (isAbortError(error)) {
+ throw new ProjectsRequestTimeoutError();
+ }
+ throw error;
+ } finally {
+ window.clearTimeout(timeoutId);
+ }
}
}
diff --git a/src/index.tsx b/src/index.tsx
index 06bf5aa..7fc7a2b 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -3,7 +3,7 @@ import { render } from "react-dom";
import App from "./App";
import { PROJECTS, USERS } from "./api/data";
-import { ProjectData, ProjectsResponse, SERVER, UserData } from "./api";
+import { ProjectData, ProjectsRequestTimeoutError, ProjectsResponse, SERVER, UserData } from "./api";
const rootElement = document.getElementById("root");
render(, rootElement);
@@ -16,18 +16,26 @@ render(, rootElement);
let hasMoreResults = true;
let lastProject: ProjectData | undefined = undefined;
- while (hasMoreResults) {
- const page: ProjectsResponse = await SERVER.getProjects({ pageSize, startAfter: lastProject, userId: user?.id?.toString() });
- if (page.hasMoreResults && page.projects.length < pageSize) {
- console.log(
- `❌ ${userString} // Improperly sized page - hasMoreResults: true but results.length < pageSize`
- );
- console.groupEnd();
+ try {
+ while (hasMoreResults) {
+ const page: ProjectsResponse = await SERVER.getProjects({ pageSize, startAfter: lastProject, userId: user?.id?.toString() });
+ if (page.hasMoreResults && page.projects.length < pageSize) {
+ console.log(
+ `❌ ${userString} // Improperly sized page - hasMoreResults: true but results.length < pageSize`
+ );
+ console.groupEnd();
+ return;
+ }
+ totalCountFromApi += page.projects.length;
+ hasMoreResults = page.hasMoreResults;
+ lastProject = page.projects[page.projects.length - 1];
+ }
+ } catch (error) {
+ if (error instanceof ProjectsRequestTimeoutError) {
+ console.log(`❌ ${userString} // ${error.message}`);
return;
}
- totalCountFromApi += page.projects.length;
- hasMoreResults = page.hasMoreResults;
- lastProject = page.projects[page.projects.length - 1];
+ throw error;
}
const filterCallback = user ? (project: ProjectData) => project.creatorId === user.id : undefined;
@@ -39,5 +47,7 @@ render(, rootElement);
console.log(`✅ ${userString} // Counts match: ${totalCountFromApi}`);
}
}
- Promise.all([...USERS.map(testUser), testUser()]);
-}
\ No newline at end of file
+ Promise.all([...USERS.map(testUser), testUser()]).catch((error) => {
+ console.error(error);
+ });
+}