UNPKG

aveazul

Version:

Bluebird drop-in replacement built on native Promise

126 lines 4.99 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Disposer } from "./disposer.js"; import { isPromise } from "./util.js"; import { AggregateError } from "@jchip/error"; const SYM_FN_DISPOSE = Symbol("fnDispose"); /** * The using function is a utility function that allows you to acquire resources, * process them, and then dispose of them in an error-safe manner. * * @param resources - An array of resources to acquire. * @param handler - A function that will be called with the acquired resources. * @param PromiseCtor - The Promise implementation to use. AveAzul or Bluebird. * @param asArray - Whether to return the result as an array. * @returns A promise that resolves to the result of the handler function. */ export function using(resources, handler, PromiseCtor, asArray) { if (typeof handler !== "function") { throw new TypeError("handler must be a function"); } // resources is guaranateed to be an array of disposer, promise like, or any value // first process all resources by mapping the resources array: // 1. if it's a disposer, get its promise and resolve its value // 2. if it's a promise like, get its value // 3. otherwise, return the value // Expect Promise to be AveAzul or Bluebird that has map method const acquisitionErrors = []; // Helper function to process a disposer const processDisposer = async (resource, disposer) => { try { const res = await disposer._promise; resource._result = res; resource[SYM_FN_DISPOSE] = disposer._data; } catch (error) { acquisitionErrors.push(error); resource._error = error; } return resource; }; // Helper to check if something is a disposer const isDisposer = (obj) => obj != null && (obj instanceof Disposer || (obj._promise !== undefined && typeof obj._data === "function")); const acquireResources = () => { const promiseRes = resources.map((resource) => { // if it's a promise-like, wait for its resolved value if (isPromise(resource)) { return { ___promise: resource }; } return resource; }); return PromiseCtor.map(promiseRes, async (resource) => { // If it's directly a disposer if (isDisposer(resource)) { return processDisposer({}, resource); } // if it's a promise like, wait for its resolved value if (resource && resource.___promise) { try { const res = await resource.___promise; // Check if the resolved value is a disposer if (isDisposer(res)) { return processDisposer(resource, res); } else { resource._result = res; } } catch (error) { acquisitionErrors.push(error); resource._error = error; } return resource; } return { _result: resource }; }); }; const disposeResources = (processedResources) => { const errors = []; return (PromiseCtor.each(processedResources, async (resource) => { // dispose all resources that were acquired without errors if (resource && resource[SYM_FN_DISPOSE]) { try { await resource[SYM_FN_DISPOSE](resource._result); } catch (error) { errors.push(error); } } })).finally(() => { if (errors.length > 0) { PromiseCtor.___throwUncaughtError(new AggregateError(errors, "cleanup resources failed")); } }); }; return acquireResources().then((processedResources) => { if (acquisitionErrors.length > 0) { return disposeResources(processedResources).tap(() => { throw acquisitionErrors[0]; }); } // now collect all the results into an array const results = []; for (const resource of processedResources) { results.push(resource._result); } let handlerPromise; try { // now call the handler with the results handlerPromise = PromiseCtor.resolve(asArray ? handler(results) : handler(...results)); } catch (error) { // catch sync error from handler handlerPromise = PromiseCtor.reject(error); } return handlerPromise .tap(() => { return disposeResources(processedResources); }) .tapCatch(() => { return disposeResources(processedResources); }); }); } //# sourceMappingURL=using.js.map