Skip to content

Instantly share code, notes, and snippets.

@ggoodman
Created February 25, 2021 15:14
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ggoodman/75ee9b4169b92e279d8307b9c2719b5a to your computer and use it in GitHub Desktop.
Save ggoodman/75ee9b4169b92e279d8307b9c2719b5a to your computer and use it in GitHub Desktop.
Using the concept of defer from golang to simplify resource cleanup in Javascript

The withCleanup helper

Example

const result = await withCleanup(async (defer) => {
  const fileHandle = await getFileHandle();
  defer(() => fileHandle.close());
   
  // Carry on
  
  return fileHandle.read();
});

Usage

The withCleanup accepts a function as an argument. That function will be passed a defer function reference. The defer function is a mechanism to register some clean-up work that will happen at the completion of the function.

The defer function should be called with a function as an argument. This function will be stored and called in LIFO order when the wrapped function's returned promise settles. Errors thrown in clean-up functions will not prevent other clean-up functions from running and will instead be accumulated into an AggregateError and thrown at the end.

type DeferredCleanupFunction = (...args: any[]) => any;
type WithCleanupFunction = (deferredFn: DeferredCleanupFunction) => void;
export async function withCleanup<TFunc extends (defer: WithCleanupFunction) => Promise<any>>(
func: TFunc
): Promise<ReturnType<TFunc>> {
const onCleanup: DeferredCleanupFunction[] = [];
const defer: WithCleanupFunction = (deferredFn) => {
onCleanup.unshift(deferredFn);
};
try {
return await func(defer);
} finally {
let errors = [];
for (const onCleanupFn of onCleanup) {
try {
await onCleanupFn();
} catch (err) {
errors.push(err);
}
}
if (errors.length) {
throw new AggregateError(errors, 'Exceptions caught while executing deferred functions');
}
}
}
export class AggregateError extends Error {
constructor(readonly errors: Iterable<Error>, readonly message: string = '') {
super(message);
this.name = 'AggregateError';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment