UNPKG

aveazul

Version:

Bluebird drop-in replacement built on native Promise

143 lines (126 loc) 4.4 kB
"use strict"; const { Disposer } = require("./disposer"); const { isPromise } = require("./util"); const { AggregateError } = require("@jchip/error"); const SYM_FN_DISPOSE = Symbol("fnDispose"); /** * @description * 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 {Array} resources - An array of resources to acquire. * @param {Function} handler - A function that will be called with the acquired resources. * @param {Promise} Promise - The Promise implementation to use. AveAzul or Bluebird. * @param {boolean} asArray - Whether to return the result as an array. * @returns {Promise} A promise that resolves to the result of the handler function. */ function using(resources, handler, Promise, 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 && (obj instanceof Disposer || (obj._promise && 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 Promise.map(promiseRes, async (resource) => { // If it's directly a disposer if (isDisposer(resource)) { return processDisposer(resource, 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 Promise.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) { Promise.___throwUncaughtError( new AggregateError(errors, "cleanup resources failed", errors) ); } }); }; 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 = Promise.resolve( asArray ? handler(results) : handler(...results) ); } catch (error) { // catch sync error from handler handlerPromise = Promise.reject(error); } return handlerPromise .tap(() => { return disposeResources(processedResources); }) .tapCatch(() => { return disposeResources(processedResources); }); }); } module.exports.using = using;