diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 43478177c1..7af21ba1e1 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,2 +1,3 @@ export * from "./colors" export * from "./control" +export * from "./schedule" diff --git a/packages/runtime/src/main.ts b/packages/runtime/src/main.ts index 98f3e6a244..87006555fb 100644 --- a/packages/runtime/src/main.ts +++ b/packages/runtime/src/main.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "@devicescript/test" -import { pixelBuffer, rgb, setStatusLight, uptime } from "." +import { pixelBuffer, rgb, schedule, setStatusLight, uptime } from "." import { delay } from "@devicescript/core" describe("rgb", () => { @@ -50,3 +50,42 @@ describe("control", () => { await setStatusLight(0x00ff00) }) }) + +describe("schedule", () => { + test("timeout", async () => { + let called = 0 + schedule( + () => { + called++ + }, + { timeout: 50 } + ) + await delay(100) + console.log({ called }) + expect(called).toBe(1) + }) + test("interval", async () => { + let called = 0 + schedule( + () => { + called++ + }, + { interval: 40 } + ) + await delay(100) + console.log({ called }) + expect(called === 2).toBe(true) + }) + test("timeout+interval", async () => { + let called = 0 + schedule( + () => { + called++ + }, + { interval: 50, timeout: 20 } + ) + await delay(100) + console.log({ called }) + expect(called === 2).toBe(true) + }) +}) diff --git a/packages/runtime/src/schedule.ts b/packages/runtime/src/schedule.ts new file mode 100644 index 0000000000..42452fa95a --- /dev/null +++ b/packages/runtime/src/schedule.ts @@ -0,0 +1,48 @@ +import { AsyncVoid } from "@devicescript/core" + +/** + * Schedules a handler to be called at a later time. Executes timeout before interval if combined. + * + * @param handler function to execute + * @param options options to configure the scheduling + * @returns clear timer function + */ +export function schedule( + handler: () => AsyncVoid, + options: { + /** + * Time in milliseconds to wait before the first execution of the handler. + */ + timeout?: number + /** + * Time in milliseconds to wait before executing the handler in an internval. + */ + interval?: number + } +) { + let timerId: number + let intervalId: number + const unsub = () => { + if (timerId) clearTimeout(timerId) + if (intervalId) clearInterval(intervalId) + timerId = intervalId = undefined + } + + if (!handler) return unsub + + let { interval, timeout } = options + if (interval === undefined && timeout === undefined) timeout = 20 + if (timeout >= 0 && interval >= 0) { + timerId = setTimeout(async () => { + await handler() + // check if cancelled or schedule + if (timerId !== undefined) + intervalId = setInterval(handler, interval) + }, 20) + } else if (timeout) { + timerId = setTimeout(handler, timeout) + } else if (interval) { + intervalId = setInterval(handler, interval) + } + return unsub +} diff --git a/website/docs/getting-started/samples/github-build-status.mdx b/website/docs/getting-started/samples/github-build-status.mdx index e1049b84b6..f6078ce059 100644 --- a/website/docs/getting-started/samples/github-build-status.mdx +++ b/website/docs/getting-started/samples/github-build-status.mdx @@ -138,7 +138,7 @@ setInterval(async () => { ```ts import { readSetting } from "@devicescript/settings" import { fetch } from "@devicescript/net" -import { setStatusLight } from "@devicescript/runtime" +import { schedule, setStatusLight } from "@devicescript/runtime" // read configuration from ./env.defaults const owner = await readSetting("GITHUB_OWNER") @@ -167,22 +167,24 @@ setInterval(async () => { await setStatusLight(c) }, 500) -// query github every 5s -setInterval(async () => { - const res = await fetch( - `https://api.github.com/repos/${owner}/${repo}/commits/${ref}/status`, - { - headers: { - Accept: "application/vnd.github+json", - Authorization: token ? `Bearer ${token}` : undefined, - "X-GitHub-Api-Version": "2022-11-28", - }, - } - ) - if (res.status === 200) { - const json = await res.json() - state = json.state - console.log({ json, state }) - } else state = "error" -}, 5000) +schedule( + async () => { + const res = await fetch( + `https://api.github.com/repos/${owner}/${repo}/commits/${ref}/status`, + { + headers: { + Accept: "application/vnd.github+json", + Authorization: token ? `Bearer ${token}` : undefined, + "X-GitHub-Api-Version": "2022-11-28", + }, + } + ) + if (res.status === 200) { + const json = await res.json() + state = json.state + console.log({ json, state }) + } else state = "error" + }, + { timeout: 1000, interval: 60000 } +) ```