kafkajs
Version:
A modern Apache Kafka client for node.js
78 lines (63 loc) • 2.4 kB
JavaScript
const { KafkaJSNumberOfRetriesExceeded, KafkaJSNonRetriableError } = require('../errors')
const isTestMode = process.env.NODE_ENV === 'test'
const RETRY_DEFAULT = isTestMode ? require('./defaults.test') : require('./defaults')
const random = (min, max) => {
return Math.random() * (max - min) + min
}
const randomFromRetryTime = (factor, retryTime) => {
const delta = factor * retryTime
return Math.ceil(random(retryTime - delta, retryTime + delta))
}
const UNRECOVERABLE_ERRORS = ['RangeError', 'ReferenceError', 'SyntaxError', 'TypeError']
const isErrorUnrecoverable = e => UNRECOVERABLE_ERRORS.includes(e.name)
const isErrorRetriable = error =>
(error.retriable || error.retriable !== false) && !isErrorUnrecoverable(error)
const createRetriable = (configs, resolve, reject, fn) => {
let aborted = false
const { factor, multiplier, maxRetryTime, retries } = configs
const bail = error => {
aborted = true
reject(error || new Error('Aborted'))
}
const calculateExponentialRetryTime = retryTime => {
return Math.min(randomFromRetryTime(factor, retryTime) * multiplier, maxRetryTime)
}
const retry = (retryTime, retryCount = 0) => {
if (aborted) return
const nextRetryTime = calculateExponentialRetryTime(retryTime)
const shouldRetry = retryCount < retries
const scheduleRetry = () => {
setTimeout(() => retry(nextRetryTime, retryCount + 1), retryTime)
}
fn(bail, retryCount, retryTime)
.then(resolve)
.catch(e => {
if (isErrorRetriable(e)) {
if (shouldRetry) {
scheduleRetry()
} else {
reject(
new KafkaJSNumberOfRetriesExceeded(e, { retryCount, retryTime, cause: e.cause || e })
)
}
} else {
reject(new KafkaJSNonRetriableError(e, { cause: e.cause || e }))
}
})
}
return retry
}
/**
* @typedef {(fn: (bail: (err: Error) => void, retryCount: number, retryTime: number) => any) => Promise<ReturnType<fn>>} Retrier
*/
/**
* @param {import("../../types").RetryOptions} [opts]
* @returns {Retrier}
*/
module.exports = (opts = {}) => fn => {
return new Promise((resolve, reject) => {
const configs = Object.assign({}, RETRY_DEFAULT, opts)
const start = createRetriable(configs, resolve, reject, fn)
start(randomFromRetryTime(configs.factor, configs.initialRetryTime))
})
}