Last active
January 22, 2024 20:46
-
-
Save ianmartorell/73a544a7d1a83d305dc846f2ecb6b5f3 to your computer and use it in GitHub Desktop.
inngest.send with retries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { retryFunction } from '@lib/utils/retryFunction'; | |
import { ClientOptions, EventSchemas, EventsFromOpts, Inngest } from 'inngest'; | |
import { SendEventPayload } from 'inngest/helpers/types'; | |
import { Events } from './events'; | |
const inngestOptions = { | |
id: 'my-app', | |
schemas: new EventSchemas().fromRecord<Events>(), | |
} satisfies ClientOptions; | |
export const inngest = new Inngest(inngestOptions); | |
/** | |
* Sends an event to Inngest with retry functionality. If the event | |
* fails to send we will log it without bubbling up the error. | |
* | |
* @param event The event payload to send. | |
*/ | |
export async function sendInngestEvent< | |
Payload extends SendEventPayload<EventsFromOpts<typeof inngestOptions>>, | |
>(event: Payload) { | |
const sendInngestEvent = async () => await inngest.send(event); | |
try { | |
await retryFunction(sendInngestEvent); | |
} catch (error) { | |
const message = error instanceof Error ? error.message : error; | |
logger.error(`Failed to send Inngest event: ${message}`, { error, event }); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { logger } from '@lib/server/logger'; | |
/** | |
* This function retries a given function a specified number of times. | |
* The function will be run at most 1 + `retries` times. | |
* | |
* Example output: | |
* | |
* Running sendInngestEvent: 1 of 4 | |
* Error: fetch failed | |
* Retrying in 2s... { runCount: 1, retries: 3 } | |
* Running sendInngestEvent: 2 of 4 | |
* Error: fetch failed | |
* Retrying in 4s... { runCount: 2, retries: 3 } | |
* Running sendInngestEvent: 3 of 4 | |
* Error: fetch failed | |
* Retrying in 6s... { runCount: 3, retries: 3 } | |
* Running sendInngestEvent: 4 of 4 | |
* Error: fetch failed | |
* Max retries reached { | |
* "runCount": 4, | |
* "retries": 3 | |
* } | |
*/ | |
export async function retryFunction<T>( | |
fn: () => T | Promise<T>, | |
options: { runCount: number; retries: number } = { | |
runCount: 1, | |
retries: 3, | |
} | |
): Promise<T> { | |
logger.debug(`Running ${fn.name}: ${options.runCount} of ${options.retries + 1}`); | |
try { | |
return await fn(); | |
} catch (error: unknown) { | |
if (error instanceof Error) { | |
logger.error(`Error: ${error.message}`); | |
} else { | |
logger.error(error); | |
} | |
// Throw if we've reached the max number of retries | |
if (options.runCount >= options.retries + 1) { | |
logger.error('Max retries reached', options); | |
throw error || new Error('Max retries reached'); | |
} | |
// Else retry with an incremental backoff time | |
const wait = 2000 * options.runCount; | |
logger.debug(`Retrying in ${wait / 1000}s...`, options); | |
return new Promise((resolve, reject) => { | |
setTimeout( | |
() => | |
retryFunction(fn, { | |
runCount: options.runCount + 1, | |
retries: options.retries, | |
}).then(resolve, reject), | |
wait | |
); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment