diff --git a/async/delay_test.ts b/async/delay_test.ts index e6d15b7d99d2..2ab339251767 100644 --- a/async/delay_test.ts +++ b/async/delay_test.ts @@ -106,7 +106,7 @@ Deno.test("delay() handles already aborted signal", async () => { assertIsDefaultAbortReason(cause); }); -Deno.test("delay() handles persitent option", async () => { +Deno.test("delay() handles persistent option", async () => { using unrefTimer = stub(Deno, "unrefTimer"); await delay(100, { persistent: false }); assertSpyCalls(unrefTimer, 1); diff --git a/async/deno.json b/async/deno.json index 65ea98e36b8f..80f9324f61ea 100644 --- a/async/deno.json +++ b/async/deno.json @@ -13,6 +13,7 @@ "./retry": "./retry.ts", "./unstable-retry": "./unstable_retry.ts", "./tee": "./tee.ts", - "./unstable-throttle": "./unstable_throttle.ts" + "./unstable-throttle": "./unstable_throttle.ts", + "./unstable-wait-for": "./unstable_wait_for.ts" } } diff --git a/async/unstable_wait_for.ts b/async/unstable_wait_for.ts new file mode 100644 index 000000000000..ca71dafd638e --- /dev/null +++ b/async/unstable_wait_for.ts @@ -0,0 +1,63 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { deadline } from "./deadline.ts"; + +/** Options for {@linkcode waitFor}. */ +export interface WaitForOptions { + /** Signal used to abort the waitFor. */ + signal?: AbortSignal; + /** Indicates the step jump in time to wait for the predicate to be true. + * + * @default {100} + */ + step?: number; +} + +/** + * Resolve a {@linkcode Promise} after a given predicate becomes true or the + * timeout amount of milliseconds has been reached. + * + * @throws {DOMException} If signal is aborted before either the waitFor + * predicate is true or the timeout duration was reached, and `signal.reason` + * is undefined. + * @param predicate a Nullary (no arguments) function returning a boolean + * @param ms Duration in milliseconds for how long the waitFor should last. + * @param options Additional options. + * + * @example Basic usage + * ```ts ignore + * import { waitFor } from "@std/async/unstable-wait-for"; + * + * // Deno server to acknowledge reception of request/webhook + * let requestReceived = false; + * Deno.serve((_req) => { + * requestReceived = true; + * return new Response("Hello, world"); + * }); + * + * // ... + * waitFor(() => requestReceived, 10000); + * // If less than 10 seconds pass, the requestReceived flag will be true + * // assert(requestReceived); + * // ... + * ``` + */ +export function waitFor( + predicate: () => boolean | Promise, + ms: number, + options: WaitForOptions = {}, +): Promise { + const { step = 100 } = options; + + // Create a new promise that resolves when the predicate is true + let interval: number; + const p: Promise = new Promise(function (resolve) { + interval = setInterval(() => { + if (predicate()) resolve(); + }, step); + }); + + // Return a deadline promise + return deadline(p, ms, options).finally(() => clearInterval(interval)); +} diff --git a/async/unstable_wait_for_test.ts b/async/unstable_wait_for_test.ts new file mode 100644 index 000000000000..2c286413d074 --- /dev/null +++ b/async/unstable_wait_for_test.ts @@ -0,0 +1,28 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertAlmostEquals, assertEquals, assertRejects } from "@std/assert"; +import { waitFor } from "./unstable_wait_for.ts"; + +// NOT detecting leaks means that the internal interval was correctly cleared + +Deno.test("waitFor() returns fulfilled promise", async () => { + let flag = false; + setTimeout(() => flag = true, 100); + const start = Date.now(); + await waitFor(() => flag === true, 1000); + // Expects the promise to be resolved after 100ms + assertAlmostEquals(Date.now() - start, 100, 10); +}); + +Deno.test("waitFor() throws DOMException on timeout", async () => { + let flag = false; + const id = setTimeout(() => flag = true, 1000); + const start = Date.now(); + const error = await assertRejects( + () => waitFor(() => flag === true, 100), + DOMException, + "Signal timed out.", + ); + assertAlmostEquals(Date.now() - start, 100, 10); + assertEquals(error.name, "TimeoutError"); + clearTimeout(id); +});