promising-utils
Version:
A utility library for promises, support both esm and commonjs
208 lines (201 loc) • 5.06 kB
JavaScript
// src/timeout.ts
var timeout = (fn, { duration = Infinity, timeoutMsg }) => {
return new Promise((resolve, reject) => {
let timer = void 0;
if (Number.isFinite(duration)) {
timeoutMsg = timeoutMsg ?? `timed out after ${duration} milliseconds`;
timer = setTimeout(() => {
reject(new Error(timeoutMsg));
}, duration);
}
fn().then((val) => {
clearTimeout(timer);
resolve(val);
}).catch((err) => {
clearTimeout(timer);
reject(err);
});
});
};
// src/wait.ts
var wait = (duration) => new Promise((resolve) => {
setTimeout(resolve, duration);
});
// src/utils.ts
var isNumber = (val) => {
return typeof val === "number";
};
var isUndefinedOrNull = (val) => {
return val === void 0 || val === null;
};
var isFunction = (val) => {
return typeof val === "function";
};
// src/retry.ts
var retry = (fn, { retries = Infinity, retryIf, retryUntil, delay, timeout: timeout2 } = {}) => {
if (!isNumber(retries) || retries < 0) {
throw new Error("retries must to be greater than or equal to 0");
}
if (!isUndefinedOrNull(timeout2)) {
if (!isNumber(timeout2)) {
throw new Error("timeout must be a milliseconds");
}
if (timeout2 < 0) {
throw new Error("timeout must be greater than or equal to 0");
}
}
let count = 0;
return new Promise((resolve, reject) => {
const fnToRun = timeout2 ? () => timeout(fn, { duration: timeout2 }) : fn;
const run = () => {
fnToRun().then(resolve).catch(async (err) => {
if (count >= retries) {
return reject(err);
}
if (retryIf && isFunction(retryIf) && !retryIf(err, count)) {
return reject(err);
}
if (retryUntil && isFunction(retryUntil) && retryUntil(err, count)) {
return reject(err);
}
count++;
if (delay) {
if (isNumber(delay)) {
await wait(delay);
} else {
await wait(delay(count));
}
}
run();
});
};
run();
});
};
// src/parallel.ts
var parallel = async (arr, {
concurrency = Infinity,
timeout: timeout2,
stopOnReject,
retries = 0
} = {}) => {
if (concurrency <= 0) {
throw new Error("concurrency must be greater than 0");
}
if (timeout2 !== void 0 && timeout2 <= 0) {
throw new Error("timeout should be greater than 0");
}
if (retries < 0) {
throw new Error("retries should be greater than or equal to 0");
}
concurrency = Math.min(arr.length, concurrency);
const result = Array(arr.length).fill(void 0);
let index = 0;
let count = 0;
return new Promise((resolve, reject) => {
const runNext = () => {
if (count === arr.length) {
return resolve(result);
}
const curIndex = index;
index++;
if (curIndex < arr.length) {
const promise = retry(arr[curIndex], {
retries,
timeout: timeout2
});
promise.then((value) => {
count++;
result[curIndex] = {
status: "fulfilled",
value
};
runNext();
}).catch((err) => {
count++;
result[curIndex] = {
status: "rejected",
reason: err
};
if (stopOnReject) {
return resolve(result);
}
runNext();
});
}
};
for (let i = 0; i < concurrency; i++) {
runNext();
}
});
};
// src/batch.ts
var batch = async (fns, {
size,
delay,
onBatchStart,
onBatchEnd,
stopOnReject,
waterFall = false,
waterFallInitialValue
} = {}) => {
if (isUndefinedOrNull(size)) {
size = fns.length;
}
const result = [];
for (let i = 0; i < fns.length; i += size) {
onBatchStart == null ? void 0 : onBatchStart(i);
const promises = fns.slice(i, i + size).map((fn, index) => {
const lastRes = result[i + index - size];
const nextInput = waterFall ? (lastRes == null ? void 0 : lastRes.status) === "fulfilled" ? lastRes == null ? void 0 : lastRes.value : (lastRes == null ? void 0 : lastRes.reason) ?? waterFallInitialValue : void 0;
return fn(nextInput);
});
const res = await Promise.allSettled(promises);
let hasError = false;
for (const r of res) {
result.push(r);
if (!hasError && r.status === "rejected") {
hasError = true;
}
}
onBatchEnd == null ? void 0 : onBatchEnd(i);
if (stopOnReject && hasError) {
break;
}
if (delay) {
if (isNumber(delay)) {
await wait(delay);
} else {
await wait(delay(i));
}
}
}
return result;
};
// src/series.ts
var series = async (fns, {
delay,
stopOnReject,
waterFall,
onEachStart,
onEachEnd,
waterFallInitialValue
} = {}) => {
return batch(fns, {
size: 1,
onBatchStart: onEachStart,
onBatchEnd: onEachEnd,
delay,
stopOnReject,
waterFall,
waterFallInitialValue
});
};
export {
batch,
parallel,
retry,
series,
timeout,
wait
};