-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- load data on init - always return latest successful data - update data in background on each request - all without data races!
- Loading branch information
Showing
3 changed files
with
128 additions
and
28 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// Doesn't work with Deno! | ||
// In order to run tests, install vitest globally using some node.js based package manager and then run `vitest` | ||
|
||
import { describe, expect, test, vi } from "vitest"; | ||
import { useFreshData } from "./util.ts"; | ||
|
||
describe("useFreshData", () => { | ||
test("fetches data before first get", async () => { | ||
let called = false; | ||
|
||
useFreshData(() => { | ||
called = true; | ||
return Promise.resolve(null); | ||
}); | ||
|
||
await expect.poll(() => called).toBe(true); | ||
}); | ||
|
||
test("initial call fails, then succeeds", async () => { | ||
const data = vi.fn().mockRejectedValueOnce("foo").mockResolvedValueOnce( | ||
"ok", | ||
); | ||
|
||
const { get } = useFreshData(data); | ||
|
||
expect(() => get()).rejects.toThrow("foo"); | ||
await expect.poll(() => get()).toBe("ok"); | ||
}); | ||
|
||
test("initially ok, then fails, then ok", async () => { | ||
const data = vi.fn().mockResolvedValueOnce(1).mockRejectedValueOnce("foo") | ||
.mockResolvedValueOnce(2); | ||
|
||
const { get } = useFreshData(data); | ||
|
||
expect(await get()).toBe(1); | ||
// waitUntil disallows errors | ||
vi.waitUntil(async () => { | ||
const value = await get(); | ||
return value === 2; | ||
}); | ||
}); | ||
|
||
test("updates data 5 times in a row", async () => { | ||
const { get } = useFreshData( | ||
vi.fn() | ||
.mockResolvedValueOnce(1) | ||
.mockResolvedValueOnce(2) | ||
.mockResolvedValueOnce(3) | ||
.mockResolvedValueOnce(4) | ||
.mockResolvedValueOnce(5), | ||
); | ||
|
||
expect(await get()).toBe(1); | ||
expect(await get()).toBe(1); | ||
expect(await get()).toBe(2); | ||
expect(await get()).toBe(3); | ||
expect(await get()).toBe(4); | ||
}); | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
export function useFreshData<T>( | ||
fn: () => Promise<T>, | ||
): { get: () => Promise<T> } { | ||
let state: { data: null | { some: T }; promise: null | Promise<T> } = { | ||
data: null, | ||
promise: null, | ||
}; | ||
|
||
function createPromise(): Promise<T> { | ||
const promise = fn().then((data) => { | ||
if (state.promise === promise) { | ||
state.data = { some: data }; | ||
state.promise = null; | ||
} | ||
return data; | ||
}).catch((err) => { | ||
if (state.promise === promise) { | ||
state.promise = null; | ||
} | ||
throw err; | ||
}); | ||
return promise; | ||
} | ||
|
||
async function get(): Promise<T> { | ||
if (state.data && state.promise) { | ||
return (state.data.some); | ||
} | ||
if (state.data) { | ||
const promise = createPromise(); | ||
promise.catch((err) => { | ||
console.error(err); | ||
}); | ||
state.promise = promise; | ||
return (state.data.some); | ||
} | ||
if (state.promise) { | ||
const data = await state.promise; | ||
state = { data: { some: data }, promise: null }; | ||
return data; | ||
} | ||
const promise = createPromise(); | ||
state = { data: null, promise }; | ||
try { | ||
const data = await promise; | ||
state = { data: { some: data }, promise: null }; | ||
return data; | ||
} catch (err) { | ||
state.promise = null; | ||
throw err; | ||
} | ||
} | ||
|
||
get().catch((err) => { | ||
console.error(err); | ||
}); | ||
|
||
return { get }; | ||
} |