Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Finally" functionality #525

Open
scarabcoder opened this issue Dec 29, 2023 · 4 comments
Open

"Finally" functionality #525

scarabcoder opened this issue Dec 29, 2023 · 4 comments

Comments

@scarabcoder
Copy link

I don't know if there have changes made since issue #254, but I am looking for the same functionality.

My specific use case is that I'm looking to avoid race conditions where concurrent calls are made to the same function, which operates with chained .andThen() calls. To do that, I'm using the library async-mutex, which lets you create a lock instance, then wait/lock/release it in situations where you don't want concurrency.

It hinges on the fact that no matter what, you are calling release(), so at the end of my chained andThen() calls it would be ideal to have it release() regardless of whether the ResultAsync ends up being a success or failure. However, I also don't want to modify the return type of the ResultAsync chain.

Currently, my workaround is to declare my AsyncResult chain as a variable, call .then() on that AsyncResult chain and have it call release() in both the failure and success callbacks, then return the original AsyncResult. That looks like this:

const result = ResultAsync.fromSafePromise(
      lock.acquire(),
    ).andThen(() => <additional logic>);
    
    // Ensure that the lock is released even if the transaction fails
    result.then(
      () => {
        lock.release();
      },
      () => {
        lock.release();
      },
    );

    return result;

This is a little annoying, the preferred syntax would be something like:

return ResultAsync.fromSafePromise(
      lock.acquire(),
    ).andThen(() => <additional logic>)
    .finally(() => lock.release());

Where finally wouldn't modify the error or success value types being returned.

@scarabcoder
Copy link
Author

This solution is specific to my use case with async-mutex, but here's a util function I came up with to make the syntax a bit more developer-friendly (while also preserving types):

export const withLock = <T, E>(
  lock: Mutex,
  fn: () => ResultAsync<T, E>,
): ResultAsync<T, E> => {
  const result = ResultAsync.fromSafePromise(lock.acquire()).andThen(() => {
    return fn();
  });

  result.then(
    () => {
      lock.release();
    },
    () => {
      lock.release();
    },
  );
  return result;
};

Usage:

const result = await withLock(lock, () => doMyDbOperation()); // waits for a lock, locks, then runs doMyDbOperation(), before unlocking and returning the AsyncResult of doMyDBOperation
result.isOk();
result.value // the original response of doMyDbOperation

@scarabcoder
Copy link
Author

scarabcoder commented Dec 29, 2023

If there's interest in some kind of finally() function, I could open up a PR to add it. I'm thinking finally() would be called with the final Result, and would be expected to NEVER throw/return an err value. Perhaps a name like safeFinally() would be more appropriate.

@macksal
Copy link
Contributor

macksal commented Mar 16, 2024

If there's interest in some kind of finally() function, I could open up a PR to add it. I'm thinking finally() would be called with the final Result, and would be expected to NEVER throw/return an err value. Perhaps a name like safeFinally() would be more appropriate.

I'd definitely make use of this.

There are perhaps some corner cases to consider - "cleanup" operations are often asynchronous themselves e.g. closing a database connection or websocket. Errors in the cleanup could also be important since if the cleanup fails, the surrounding method might disobey its usual contract.

This makes me think that finally would need to await the result of it's callback, and possibly even propagate errors that happen there. The result would be that finally works the same as andThen, but it is called for ok and error results, and if an error actually gets thrown somewhere in the chain.

This is similar to how the finally keyword works within async functions.

@macksal
Copy link
Contributor

macksal commented Oct 1, 2024

@scarabcoder I have added this feature in #588, please review if you are interested!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants