Skip to content

Commit 5af091b

Browse files
authored
feat(async/unstable): add waitFor function to wait for condition to be true (#6230)
1 parent a2a6305 commit 5af091b

File tree

4 files changed

+94
-2
lines changed

4 files changed

+94
-2
lines changed

async/delay_test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ Deno.test("delay() handles already aborted signal", async () => {
106106
assertIsDefaultAbortReason(cause);
107107
});
108108

109-
Deno.test("delay() handles persitent option", async () => {
109+
Deno.test("delay() handles persistent option", async () => {
110110
using unrefTimer = stub(Deno, "unrefTimer");
111111
await delay(100, { persistent: false });
112112
assertSpyCalls(unrefTimer, 1);

async/deno.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"./retry": "./retry.ts",
1414
"./unstable-retry": "./unstable_retry.ts",
1515
"./tee": "./tee.ts",
16-
"./unstable-throttle": "./unstable_throttle.ts"
16+
"./unstable-throttle": "./unstable_throttle.ts",
17+
"./unstable-wait-for": "./unstable_wait_for.ts"
1718
}
1819
}

async/unstable_wait_for.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
// This module is browser compatible.
3+
4+
import { deadline } from "./deadline.ts";
5+
6+
/** Options for {@linkcode waitFor}. */
7+
export interface WaitForOptions {
8+
/** Signal used to abort the waitFor. */
9+
signal?: AbortSignal;
10+
/** Indicates the step jump in time to wait for the predicate to be true.
11+
*
12+
* @default {100}
13+
*/
14+
step?: number;
15+
}
16+
17+
/**
18+
* Resolve a {@linkcode Promise} after a given predicate becomes true or the
19+
* timeout amount of milliseconds has been reached.
20+
*
21+
* @throws {DOMException} If signal is aborted before either the waitFor
22+
* predicate is true or the timeout duration was reached, and `signal.reason`
23+
* is undefined.
24+
* @param predicate a Nullary (no arguments) function returning a boolean
25+
* @param ms Duration in milliseconds for how long the waitFor should last.
26+
* @param options Additional options.
27+
*
28+
* @example Basic usage
29+
* ```ts ignore
30+
* import { waitFor } from "@std/async/unstable-wait-for";
31+
*
32+
* // Deno server to acknowledge reception of request/webhook
33+
* let requestReceived = false;
34+
* Deno.serve((_req) => {
35+
* requestReceived = true;
36+
* return new Response("Hello, world");
37+
* });
38+
*
39+
* // ...
40+
* waitFor(() => requestReceived, 10000);
41+
* // If less than 10 seconds pass, the requestReceived flag will be true
42+
* // assert(requestReceived);
43+
* // ...
44+
* ```
45+
*/
46+
export function waitFor(
47+
predicate: () => boolean | Promise<boolean>,
48+
ms: number,
49+
options: WaitForOptions = {},
50+
): Promise<void> {
51+
const { step = 100 } = options;
52+
53+
// Create a new promise that resolves when the predicate is true
54+
let interval: number;
55+
const p: Promise<void> = new Promise(function (resolve) {
56+
interval = setInterval(() => {
57+
if (predicate()) resolve();
58+
}, step);
59+
});
60+
61+
// Return a deadline promise
62+
return deadline(p, ms, options).finally(() => clearInterval(interval));
63+
}

async/unstable_wait_for_test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
import { assertAlmostEquals, assertEquals, assertRejects } from "@std/assert";
3+
import { waitFor } from "./unstable_wait_for.ts";
4+
5+
// NOT detecting leaks means that the internal interval was correctly cleared
6+
7+
Deno.test("waitFor() returns fulfilled promise", async () => {
8+
let flag = false;
9+
setTimeout(() => flag = true, 100);
10+
const start = Date.now();
11+
await waitFor(() => flag === true, 1000);
12+
// Expects the promise to be resolved after 100ms
13+
assertAlmostEquals(Date.now() - start, 100, 10);
14+
});
15+
16+
Deno.test("waitFor() throws DOMException on timeout", async () => {
17+
let flag = false;
18+
const id = setTimeout(() => flag = true, 1000);
19+
const start = Date.now();
20+
const error = await assertRejects(
21+
() => waitFor(() => flag === true, 100),
22+
DOMException,
23+
"Signal timed out.",
24+
);
25+
assertAlmostEquals(Date.now() - start, 100, 10);
26+
assertEquals(error.name, "TimeoutError");
27+
clearTimeout(id);
28+
});

0 commit comments

Comments
 (0)