Skip to content

Commit

Permalink
feat(async/unstable): add waitFor function to wait for condition to…
Browse files Browse the repository at this point in the history
… be true (#6230)
  • Loading branch information
acrodrig authored Jan 21, 2025
1 parent a2a6305 commit 5af091b
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 2 deletions.
2 changes: 1 addition & 1 deletion async/delay_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion async/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
63 changes: 63 additions & 0 deletions async/unstable_wait_for.ts
Original file line number Diff line number Diff line change
@@ -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<boolean>,
ms: number,
options: WaitForOptions = {},
): Promise<void> {
const { step = 100 } = options;

// Create a new promise that resolves when the predicate is true
let interval: number;
const p: Promise<void> = new Promise(function (resolve) {
interval = setInterval(() => {
if (predicate()) resolve();
}, step);
});

// Return a deadline promise
return deadline(p, ms, options).finally(() => clearInterval(interval));
}
28 changes: 28 additions & 0 deletions async/unstable_wait_for_test.ts
Original file line number Diff line number Diff line change
@@ -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);
});

0 comments on commit 5af091b

Please sign in to comment.