From 3b02a17a511b2743735f566bda0a8d3556525c8c Mon Sep 17 00:00:00 2001 From: "Andres C. Rodriguez" Date: Tue, 3 Dec 2024 16:38:13 -0800 Subject: [PATCH 1/4] Adding waitFor function to async module (plus one typo fix) --- async/delay_test.ts | 2 +- async/deno.json | 3 +- async/unstable_wait_for.ts | 63 +++++++++++++++++++++++++++++++++ async/unstable_wait_for_test.ts | 27 ++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 async/unstable_wait_for.ts create mode 100644 async/unstable_wait_for_test.ts 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..792fa4593dcc --- /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 ahs 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 no-assert + * 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..df3f0a6bdb3f --- /dev/null +++ b/async/unstable_wait_for_test.ts @@ -0,0 +1,27 @@ +// 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; + 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"); +}); From 0645adba9289a441ec942296645079e52250a158 Mon Sep 17 00:00:00 2001 From: "Andres C. Rodriguez" Date: Wed, 4 Dec 2024 16:37:46 -0800 Subject: [PATCH 2/4] Fixing clearing of timeout leak --- async/unstable_wait_for_test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/async/unstable_wait_for_test.ts b/async/unstable_wait_for_test.ts index df3f0a6bdb3f..2c286413d074 100644 --- a/async/unstable_wait_for_test.ts +++ b/async/unstable_wait_for_test.ts @@ -15,7 +15,7 @@ Deno.test("waitFor() returns fulfilled promise", async () => { Deno.test("waitFor() throws DOMException on timeout", async () => { let flag = false; - setTimeout(() => flag = true, 1000); + const id = setTimeout(() => flag = true, 1000); const start = Date.now(); const error = await assertRejects( () => waitFor(() => flag === true, 100), @@ -24,4 +24,5 @@ Deno.test("waitFor() throws DOMException on timeout", async () => { ); assertAlmostEquals(Date.now() - start, 100, 10); assertEquals(error.name, "TimeoutError"); + clearTimeout(id); }); From 66cf455ece0f565b7d663692c75da21e86513f06 Mon Sep 17 00:00:00 2001 From: "Andres C. Rodriguez" Date: Wed, 4 Dec 2024 16:46:00 -0800 Subject: [PATCH 3/4] Ignoring the leak in the docs example (as it add no knowledge value about actual function) --- async/unstable_wait_for.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async/unstable_wait_for.ts b/async/unstable_wait_for.ts index 792fa4593dcc..06ff43b2f0c6 100644 --- a/async/unstable_wait_for.ts +++ b/async/unstable_wait_for.ts @@ -26,7 +26,7 @@ export interface WaitForOptions { * @param options Additional options. * * @example Basic usage - * ```ts no-assert + * ```ts ignore * import { waitFor } from "@std/async/unstable-wait-for"; * * // Deno server to acknowledge reception of request/webhook From 32764c1472ca0ecbeb686e0a6023e2723a907c89 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 5 Dec 2024 12:58:38 +0900 Subject: [PATCH 4/4] Update async/unstable_wait_for.ts --- async/unstable_wait_for.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async/unstable_wait_for.ts b/async/unstable_wait_for.ts index 06ff43b2f0c6..ca71dafd638e 100644 --- a/async/unstable_wait_for.ts +++ b/async/unstable_wait_for.ts @@ -16,7 +16,7 @@ export interface WaitForOptions { /** * Resolve a {@linkcode Promise} after a given predicate becomes true or the - * timeout amount of milliseconds ahs been reached. + * 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`