blob: 94cba8452c7ef1da8553321369d0eb5f2bed3e5c [file] [log] [blame] [edit]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* AsyncGuard<T> ensures that a given asynchronous operation does not overlap
* with itself.
*
* This class is useful in scenarios where you want to prevent concurrent
* executions of the same async function. If the function is already in
* progress, any subsequent calls to `run` will return the same promise,
* ensuring no new execution starts until the ongoing one completes.
*
* - Guarantees single execution: Only one instance of the provided async
* function will execute at a time.
* - Automatically resets: Once the function completes (either successfully
* or with an error), the guard resets and allows new executions.
*
* This class differs from AsyncLimiter in the fact that it has no queueing at
* all (AsyncLimiter instead keeps a queue of max_depth=1 and keeps over-writing
* the last task).
*
* Example:
* ```typescript
* const asyncTask = async () => {
* console.log("Task started...");
* await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate work.
* console.log("Task finished!");
* return "Result";
* };
*
* const guard = new AsyncGuard<string>();
*
* // Simultaneous calls
* guard.run(asyncTask).then(console.log); // Logs "Task started..." and
* // "Task finished!" -> "Result"
* guard.run(asyncTask).then(console.log); // Will not log "Task started..."
* // again, reuses the promise
* ```
*/
export class AsyncGuard<T> {
private pendingPromise?: Promise<T>;
/**
* Runs the provided async function, ensuring no overlap.
* If a previous call is still pending, it returns the same promise.
*
* @param func - The async function to execute.
* @returns A promise resolving to the function's result.
*/
run(func: () => Promise<T>): Promise<T> {
if (this.pendingPromise !== undefined) {
return this.pendingPromise;
}
this.pendingPromise = func();
this.pendingPromise.finally(() => {
this.pendingPromise = undefined;
});
return this.pendingPromise;
}
}