• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * AsyncGuard<T> ensures that a given asynchronous operation does not overlap
17 * with itself.
18 *
19 * This class is useful in scenarios where you want to prevent concurrent
20 * executions of the same async function. If the function is already in
21 * progress, any subsequent calls to `run` will return the same promise,
22 * ensuring no new execution starts until the ongoing one completes.
23 *
24 * - Guarantees single execution: Only one instance of the provided async
25 *   function will execute at a time.
26 * - Automatically resets: Once the function completes (either successfully
27 *   or with an error), the guard resets and allows new executions.
28 *
29 * This class differs from AsyncLimiter in the fact that it has no queueing at
30 * all (AsyncLimiter instead keeps a queue of max_depth=1 and keeps over-writing
31 * the last task).
32 *
33 * Example:
34 * ```typescript
35 * const asyncTask = async () => {
36 *   console.log("Task started...");
37 *   await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate work.
38 *   console.log("Task finished!");
39 *   return "Result";
40 * };
41 *
42 * const guard = new AsyncGuard<string>();
43 *
44 * // Simultaneous calls
45 * guard.run(asyncTask).then(console.log); // Logs "Task started..." and
46 *                                         // "Task finished!" -> "Result"
47 * guard.run(asyncTask).then(console.log); // Will not log "Task started..."
48 *                                         // again, reuses the promise
49 * ```
50 */
51export class AsyncGuard<T> {
52  private pendingPromise?: Promise<T>;
53
54  /**
55   * Runs the provided async function, ensuring no overlap.
56   * If a previous call is still pending, it returns the same promise.
57   *
58   * @param func - The async function to execute.
59   * @returns A promise resolving to the function's result.
60   */
61  run(func: () => Promise<T>): Promise<T> {
62    if (this.pendingPromise !== undefined) {
63      return this.pendingPromise;
64    }
65    this.pendingPromise = func();
66    this.pendingPromise.finally(() => {
67      this.pendingPromise = undefined;
68    });
69    return this.pendingPromise;
70  }
71}
72