Skip to content

Instantly share code, notes, and snippets.

@lwillmeth
Last active September 11, 2019 23:22
Show Gist options
  • Save lwillmeth/6c29c3ca8a0b65f514f0018cb845e33c to your computer and use it in GitHub Desktop.
Save lwillmeth/6c29c3ca8a0b65f514f0018cb845e33c to your computer and use it in GitHub Desktop.
const defaultOptions = Object.freeze({
delayMs: 0, // (ms delay on all invocations)
retries: 3, // (retry failed promises up to 3 times each)
errorDelayMs: 100, // (ms delay after first error)
exponential: true, // (if true then double errorDelayMs on consecutive errors)
throwOnError: true, // (set to false to only log errors that fail to retry)
keepErrors: false, // (set to true to return an Array in `.errors`)
logger: console, // (set to initialized lib-logger-nodejs)
verbose: process.env.VERBOSE || false, // (set to true to log summary of successes and errors every time)
errorMsg: '' // (additional error message to display if all retries fail)
});
const NUM_ITEMS = 10;
const DELAY = 50;
let totalRunTime = 0;
async function batchRetry(items, fn, options) {
const ops = { ...defaultOptions, ...options };
// build up hashes to maintain original order
const successes = {};
const errors = {};
let index = ops.batchSize;
async function startPromise(i, item) {
// console.log('kicking off item:', item.id);
return fn(item)
.then(res => successes[i] = res)
.catch(err => errors[i] = err)
.then(() => {
if (index < items.length) {
return startPromise(index, items[index++]);
}
})
}
const firstBatch = items.slice(0, ops.batchSize);
const activePromises = firstBatch.map((item, i) => startPromise(i, item));
// resolve all promises without bailing if the first batch fails
for(let i=0; i<activePromises.length; i++) {
await activePromises[i];
}
// flatten the hashes into arrays
return { successes: Object.values(successes), errors: Object.values(errors) };
}
async function waitForSomething(i) {
return new Promise((resolve, reject) => {
// introduce random failures
const result = Math.random() > 0.5 ? resolve : reject;
const thisRunTime = DELAY * (1 + Math.random()); // 50-100 ms
totalRunTime += thisRunTime; // keep a running total of async thread time just for fun
setTimeout(() => result(i), thisRunTime)
});
}
async function demo(){
const items = [];
for (let i = 0; i < NUM_ITEMS; ++i) {
items.push({ id: i });
}
const startedAt = Date.now();
const results = await batchRetry(items, waitForSomething, { batchSize: 5, retries: 3 });
const timeToRunAll = Date.now() - startedAt;
console.log(`Results => ${results.successes.length} successes and ${results.errors.length} failures.`);
console.log(`Successes: ${results.successes.map(i => i.id).join(',')}.`);
console.log(`Errors: ${results.errors.map(i => i.id).join(',')}.`);
console.log(`It took ${timeToRunAll} ms to run ${NUM_ITEMS} items, with a total thread time of ${totalRunTime} ms.`);
}
demo();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment