Skip to content

Instantly share code, notes, and snippets.

@Justin-Credible
Last active November 14, 2023 21:15
Show Gist options
  • Save Justin-Credible/1adc7dd5f1c06964771258e0c980e3da to your computer and use it in GitHub Desktop.
Save Justin-Credible/1adc7dd5f1c06964771258e0c980e3da to your computer and use it in GitHub Desktop.
/**
* Normally, when using the await keyword to wait on a promise, if the promise is
* rejected then an exception will be thrown. This means that to handle a rejection
* you would need to try/catch all your await usages, which is inconvenient.
*
* This helper is meant to be used with the await keyword to wrap the promise
* so that it never throws an exception; it is guaranteed to resolve successfully.
*
* In the case of an error, it will be available in the resolved result object so
* that the caller can handle the error without a try/catch.
*
* For example, this:
*
* let data = null;
* try {
* data = await this.DataSource.retrieve();
* } catch (error) { ... }
*
* becomes this:
*
* let result = await doTry(this.DataSource.retrieve())
* if (err) { ... }
* let data = result.data;
*
* If you want added protection against uncaught exceptions, wrap with a function:
*
* let result = await doTry(() => this.DataSource.retrieve())
* if (err) { ... }
* let data = result.data;
*
* @param promise The promise to wait on.
* @returns A promise that ALWAYS resolves succesfully.
*/
export function doTry<T>(promiseOrFactory: Promise<T> | PromiseFactory<T>): Promise<DoTryResult<T>> {
let promise: Promise<T> = null;
let factoryFunctionError: any;
if (typeof(promiseOrFactory) === "function") {
// If a factory function was passed, invoke it to obtain the promise.
try {
let factory = <PromiseFactory<T>> promiseOrFactory;
promise = factory();
}
catch (error) {
// If the function threw an exception without returning a promise, then
// save off the error so we can resolve with it below.
factoryFunctionError = error;
}
}
else {
// If a promise was provided instead of a factory, use it.
// The official Promise types conflict with Angular's types. For our purposes
// it doesn't matter, we just need a .then(reject, resolve) method that we can
// invoke down below. So we'll perform a cast to this generic type here.
promise = <Promise<T>> promiseOrFactory;
}
// Create a promise wrapper and listen for the then/catch on the original promise.
// Note that we resolve successfully in both cases so a rejection will never occur.
return new Promise<DoTryResult<T>>((resolve, reject) => {
// If we don't have a promise at this point, either the factory function threw
// an exception or the caller passed a null/undefined value into the doTry() helper.
if (!promise) {
let error = factoryFunctionError ? factoryFunctionError : new Error("AsyncAwaitHelper.doTry(): No promise was provided.");
resolve({
error: error,
data: null,
});
return;
}
// If we did receive a promise, then wait for a success or rejection, and then
// resolve the wrapped promise with the custom result interface structure.
promise.then((data: T) => {
resolve({
error: null,
data: data,
});
}, (error: any) => {
resolve({
error: error,
data: null,
});
});
});
}
/**
* A factory function that returns a promise.
*/
export type PromiseFactory<T> = () => Promise<T>;
/**
* The result of the doTry() helper method for either a success or failure case.
*
* If error is populated, the promise was rejected or threw an exception.
* If data is populated, the promise resolved successfully.
*/
export interface DoTryResult<T> {
error: any;
data: T;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment