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