UNPKG

@devgrid/common

Version:
278 lines 9.49 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.universalifyFromPromise = exports.universalify = exports.try = exports.props = exports.retry = exports.finally = exports.promisifyAll = exports.promisify = exports.callbackify = exports.nodeify = exports.timeout = exports.delay = exports.defer = void 0; const entries_1 = require("./entries"); const primitives_1 = require("./primitives"); const predicates_1 = require("./predicates"); const defer = () => { const deferred = {}; deferred.promise = new Promise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; }); return deferred; }; exports.defer = defer; const delay = (ms, value, options) => new Promise((resolve) => { const timer = setTimeout(resolve, ms, value); if (options?.unref && typeof timer.unref === "function") { timer.unref(); } }); exports.delay = delay; const timeout = (promise, ms, options = {}) => { if (!(0, predicates_1.isPromise)(promise)) { throw new TypeError("The first argument must be a promise"); } if (!(0, predicates_1.isNumber)(ms) || ms <= 0) { throw new TypeError("Timeout must be a positive number"); } return new Promise((resolve, reject) => { let timeoutId; const cleanup = () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } if (options.signal) { options.signal.removeEventListener('abort', onAbort); } }; const onAbort = () => { cleanup(); reject(new Error('Timeout aborted')); }; if (options.signal) { if (options.signal.aborted) { onAbort(); return; } options.signal.addEventListener('abort', onAbort, { once: true }); } timeoutId = setTimeout(() => { cleanup(); reject(new Error(`Timeout of ${ms}ms exceeded`)); }, ms); if (options.unref && typeof timeoutId.unref === 'function') { timeoutId.unref(); } promise.then((value) => { cleanup(); resolve(value); }, (error) => { cleanup(); reject(error); }); }); }; exports.timeout = timeout; const nodeify = (promise, cb) => { if (!(0, predicates_1.isPromise)(promise)) { throw new TypeError("The first argument must be a promise"); } if (!(0, predicates_1.isFunction)(cb)) { return promise; } promise.then((x) => { cb(null, x); }, (y) => { cb(y); }); return promise; }; exports.nodeify = nodeify; const callbackify = (fn) => { if (!(0, predicates_1.isFunction)(fn)) { throw new TypeError("The first argument must be a function"); } return function _(...args) { if (args.length && (0, predicates_1.isFunction)(args[args.length - 1])) { const cb = args.pop(); return (0, exports.nodeify)(fn.apply(this, args), cb); } return fn.apply(this, args); }; }; exports.callbackify = callbackify; const processFn = (fn, context, args, multiArgs, resolve, reject) => { if (multiArgs) { args.push((...result) => { if (result[0]) { reject(result); } else { result.shift(); resolve(result); } }); } else { args.push((err, result) => { if (err) { reject(err); } else { resolve(result); } }); } fn.apply(context, args); }; const promisify = (fn, options) => { if (!(0, predicates_1.isFunction)(fn)) { throw new TypeError("The first argument must be a function"); } return options && options.context ? (...args) => new Promise((resolve, reject) => { processFn(fn, options.context, args, options && Boolean(options.multiArgs), resolve, reject); }) : function _(...args) { return new Promise((resolve, reject) => { processFn(fn, this, args, Boolean(options?.multiArgs), resolve, reject); }); }; }; exports.promisify = promisify; const promisifyAll = (source, options) => { const suffix = options && options.suffix ? options.suffix : "Async"; const filter = options && typeof options.filter === "function" ? options.filter : primitives_1.truly; if ((0, predicates_1.isFunction)(source)) { return (0, exports.promisify)(source, options); } const target = Object.create(source); for (const [key, value] of (0, entries_1.entries)(source, { all: true })) { if ((0, predicates_1.isFunction)(value) && filter(key)) { target[`${key}${suffix}`] = (0, exports.promisify)(value, options); } } return target; }; exports.promisifyAll = promisifyAll; const _finally = (promise, onFinally) => { onFinally = onFinally || primitives_1.noop; return promise.then((val) => new Promise((resolve) => { resolve(onFinally()); }).then(() => val), (err) => new Promise((resolve) => { resolve(onFinally()); }).then(() => { throw err; })); }; exports.finally = _finally; const retry = async (callback, options) => { if (!callback || !options) { throw new Error("requires a callback and an options set or a number"); } const opts = (0, predicates_1.isNumber)(options) ? { max: options } : options; const config = { $current: opts.$current || 1, max: opts.max, timeout: opts.timeout, match: Array.isArray(opts.match) ? opts.match : opts.match ? [opts.match] : [], backoffBase: opts.backoffBase ?? 100, backoffExponent: opts.backoffExponent || 1.1, report: opts.report || null, name: opts.name || callback.name || "unknown" }; const shouldRetry = (error, attempt) => { if (attempt >= config.max) return false; if (config.match.length === 0) return true; return config.match.some(match => match === error.toString() || match === error.message || ((0, predicates_1.isFunction)(match) && error instanceof match) || (match instanceof RegExp && (match.test(error.message) || match.test(error.toString())))); }; let lastError = null; while (true) { try { if (config.report) { config.report(`Attempt ${config.name} #${config.$current}`, config); } let result = callback({ current: config.$current }); if ((0, predicates_1.isPromise)(result)) { if (config.timeout) { result = await (0, exports.timeout)(result, config.timeout); } else { result = await result; } } if (config.report) { config.report(`Success ${config.name} #${config.$current}`, config); } return result; } catch (error) { lastError = error; if (config.report) { config.report(`Failed ${config.name} #${config.$current}: ${error.toString()}`, config, error); } if (!shouldRetry(error, config.$current)) { throw lastError; } const retryDelay = Math.floor(config.backoffBase * Math.pow(config.backoffExponent ?? 1.1, config.$current - 1)); config.$current++; if (retryDelay > 0) { if (config.report) { config.report(`Delaying retry of ${config.name} by ${retryDelay}ms`, config); } await (0, exports.delay)(retryDelay); } } } }; exports.retry = retry; const props = async (obj) => { const result = {}; await Promise.all(Object.keys(obj).map(async (key) => { Object.defineProperty(result, key, { enumerable: true, value: await obj[key], }); })); return result; }; exports.props = props; const try_ = (fn, ...args) => new Promise((resolve) => { resolve(fn(...args)); }); exports.try = try_; const universalify = (fn) => Object.defineProperties(function _(...args) { if ((0, predicates_1.isFunction)(args[args.length - 1])) { return fn.apply(this, args); } return new Promise((resolve, reject) => { args.push((err, res) => { if (err) { reject(err); } else { resolve(res); } }); fn.apply(this, args); }); }, { name: { value: fn.name, }, ...Object.keys(fn).reduce((props_, k) => { props_[k] = { enumerable: true, value: fn[k], }; return props_; }, {}), }); exports.universalify = universalify; const universalifyFromPromise = (fn) => Object.defineProperty(function _(...args) { const cb = args[args.length - 1]; if (!(0, predicates_1.isFunction)(cb)) { return fn.apply(this, args); } return fn.apply(this, args).then((r) => cb(null, r), cb); }, "name", { value: fn.name }); exports.universalifyFromPromise = universalifyFromPromise; //# sourceMappingURL=promise.js.map