-
-
Save ryanguill/e89931f9d223e74dabcb070879a58298 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Runs multiple queries in parallel and resolves with the first query that returns records. | |
* If no query returns records, it rejects with an error. | |
* | |
* @param queries An array of promises representing database queries. | |
* @returns A promise that resolves with the first valid result or rejects if no results are found. | |
*/ | |
export async function firstQueryWithResultsWithKey<T>({ | |
queriesAsRecord, | |
resultTest = ({ result }) => Array.isArray(result) && result.length > 0, | |
noResultsReturn = null, | |
}: { | |
queriesAsRecord: Record<string, Promise<T>>; | |
resultTest?: ({ result, key }: { result: T; key: string | null }) => boolean; | |
noResultsReturn?: T | null; | |
}): Promise<{ result: T; key: string | null }> { | |
return new Promise((resolve, reject) => { | |
let completed = false; // Track if a query has returned results | |
let pendingCount = Object.keys(queriesAsRecord).length; // Track the number of pending queries | |
if (pendingCount === 0) { | |
if (noResultsReturn) { | |
resolve({ result: noResultsReturn, key: null }); | |
} else { | |
reject(new Error("No queries returned any records")); | |
} | |
} | |
// Define a function to handle each query independently | |
const handleQuery = async (query: Promise<T>, key: string) => { | |
try { | |
if (!completed) { | |
const result = await query; | |
// If the result has records and no other query has resolved | |
if (resultTest({ result, key }) && !completed) { | |
completed = true; // Mark as completed | |
resolve({ result, key }); // Resolve with the first valid result | |
} | |
} | |
} catch (error) { | |
console.error("Query error:", error); // Log query errors | |
} finally { | |
// Decrement pending count and check if all queries are exhausted | |
pendingCount--; | |
if (pendingCount === 0 && !completed) { | |
if (noResultsReturn) { | |
resolve({ result: noResultsReturn, key: null }); | |
} else { | |
reject(new Error("No queries returned any records")); | |
} | |
} | |
} | |
}; | |
// Start all queries in parallel | |
Object.entries(queriesAsRecord).forEach(([key, query]) => | |
handleQuery(query, key), | |
); | |
}); | |
} | |
export async function firstQueryWithResults<T>({ | |
queries, | |
resultTest = (result) => Array.isArray(result) && result.length > 0, | |
noResultsReturn = null, | |
}: { | |
queries: Promise<T>[]; | |
resultTest?: (result: T) => boolean; | |
noResultsReturn?: T | null; | |
}): Promise<T> { | |
let queriesAsRecord = Object.fromEntries( | |
queries.map((query, index) => [index.toString(), query]), | |
); | |
const { result } = await firstQueryWithResultsWithKey({ | |
queriesAsRecord, | |
resultTest: ({ result }) => resultTest(result), | |
noResultsReturn, | |
}); | |
return result; | |
} | |
describe("firstQueryWithResults", () => { | |
type QueryResult = any[]; | |
const wait = (interval: number) => | |
new Promise((resolve) => setTimeout(resolve, interval)); | |
it("should handle a default resultTest", async () => { | |
const query1 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(100); | |
return []; | |
}; | |
const query2 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(200); | |
return []; | |
}; | |
const query3 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(300); | |
return [{ a: id }]; | |
}; | |
// Call the function with an array of query promises | |
let error: Error | null = null; | |
let result: QueryResult | null = null; | |
try { | |
result = await firstQueryWithResults({ | |
queries: [ | |
query1({ id: "1" }), | |
query2({ id: "2" }), | |
query3({ id: "3" }), | |
], | |
}); | |
} catch (err) { | |
error = err as Error; | |
} | |
expect(error).toBeNull(); | |
expect(result).toEqual([{ a: "3" }]); | |
}); | |
it("should return the first result from the queries 1", async () => { | |
const query1 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(100); | |
return []; | |
}; | |
const query2 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(200); | |
return []; | |
}; | |
const query3 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(300); | |
return [{ a: id }]; | |
}; | |
// Call the function with an array of query promises | |
let error: Error | null = null; | |
let result: QueryResult | null = null; | |
try { | |
result = await firstQueryWithResults({ | |
queries: [ | |
query1({ id: "1" }), | |
query2({ id: "2" }), | |
query3({ id: "3" }), | |
], | |
resultTest: (result) => result.length > 0, | |
}); | |
} catch (err) { | |
error = err as Error; | |
} | |
expect(error).toBeNull(); | |
expect(result).toEqual([{ a: "3" }]); | |
}); | |
it("should return the first result from the queries 2", async () => { | |
const query1 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(100); | |
return [{ a: id }]; | |
}; | |
const query2 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(200); | |
return []; | |
}; | |
const query3 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(300); | |
return []; | |
}; | |
// Call the function with an array of query promises | |
let error: Error | null = null; | |
let result: QueryResult | null = null; | |
try { | |
result = await firstQueryWithResults({ | |
queries: [ | |
query1({ id: "1" }), | |
query2({ id: "2" }), | |
query3({ id: "3" }), | |
], | |
resultTest: (result) => result.length > 0, | |
}); | |
} catch (err) { | |
error = err as Error; | |
} | |
expect(error).toBeNull(); | |
expect(result).toEqual([{ a: "1" }]); | |
}); | |
it("should return the first result from the queries 3", async () => { | |
const query1 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(100); | |
return []; | |
}; | |
const query2 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(200); | |
return [{ a: id }]; | |
}; | |
const query3 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(300); | |
return []; | |
}; | |
// Call the function with an array of query promises | |
let error: Error | null = null; | |
let result: QueryResult | null = null; | |
try { | |
result = await firstQueryWithResults({ | |
queries: [ | |
query1({ id: "1" }), | |
query2({ id: "2" }), | |
query3({ id: "3" }), | |
], | |
resultTest: (result) => result.length > 0, | |
}); | |
} catch (err) { | |
error = err as Error; | |
} | |
expect(error).toBeNull(); | |
expect(result).toEqual([{ a: "2" }]); | |
}); | |
it("should return an error if no queries return results", async () => { | |
const query1 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(100); | |
return []; | |
}; | |
const query2 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(200); | |
return []; | |
}; | |
const query3 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(300); | |
return []; | |
}; | |
// Call the function with an array of query promises | |
let error: Error | null = null; | |
let result: QueryResult | null = null; | |
try { | |
result = await firstQueryWithResults({ | |
queries: [ | |
query1({ id: "1" }), | |
query2({ id: "2" }), | |
query3({ id: "3" }), | |
], | |
resultTest: (result) => result.length > 0, | |
}); | |
} catch (err) { | |
error = err as Error; | |
} | |
expect(error).toBeInstanceOf(Error); | |
expect(result).toBeNull(); | |
}); | |
it("should return the noResultsReturn value if no queries return results", async () => { | |
const query1 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(100); | |
return []; | |
}; | |
const query2 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(200); | |
return []; | |
}; | |
const query3 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(300); | |
return []; | |
}; | |
let result: QueryResult | null = null; | |
result = await firstQueryWithResults({ | |
queries: [query1({ id: "1" }), query2({ id: "2" }), query3({ id: "3" })], | |
resultTest: (result) => result.length > 0, | |
noResultsReturn: [], | |
}); | |
expect(result).toEqual([]); | |
}); | |
it("should handle no queries", async () => { | |
// Call the function with an array of query promises | |
let error: Error | null = null; | |
let result: QueryResult | null = null; | |
try { | |
result = await firstQueryWithResults({ | |
queries: [], | |
resultTest: (result) => result.length > 0, | |
noResultsReturn: [], | |
}); | |
} catch (err) { | |
error = err as Error; | |
} | |
expect(error).toBeNull(); | |
expect(result).toEqual([]); | |
}); | |
it("should return the first result from the queries with key", async () => { | |
const query1 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(100); | |
return []; | |
}; | |
const query2 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(200); | |
return []; | |
}; | |
const query3 = async ({ id }: { id: string }): Promise<QueryResult> => { | |
await wait(300); | |
return [{ a: id }]; | |
}; | |
// Call the function with an array of query promises | |
let error: Error | null = null; | |
let result: { result: QueryResult; key: string | null } | null = null; | |
try { | |
result = await firstQueryWithResultsWithKey({ | |
queriesAsRecord: { | |
query1: query1({ id: "1" }), | |
query2: query2({ id: "2" }), | |
query3: query3({ id: "3" }), | |
}, | |
resultTest: ({ result }) => result.length > 0, | |
}); | |
} catch (err) { | |
error = err as Error; | |
} | |
expect(error).toBeNull(); | |
expect(result).toEqual({ result: [{ a: "3" }], key: "query3" }); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment