How to Implement Retries

Learn how to implement retries in your code to handle errors and improve application reliability.

Introduction

Alchemy is a powerful platform that provides developers with advanced blockchain tools, such as APIs, monitoring, and analytics, to build their blockchain applications faster and more efficiently. Alchemy’s Elastic Throughput system guarantees a given throughput limit measured in compute units per second, but you may still hit your throughput capacity in some cases. In this tutorial, we will explore how to implement retries to handle Alchemy 429 errors.

Option 1: Alchemy SDK

The Alchemy SDK is the easiest way to connect your dApp to the blockchain. It automatically handles retry logic for you. To use the Alchemy SDK, follow these steps:

  1. Create a new node.js project and Install the Alchemy SDK using npm or yarn:
$mkdir my-project
>cd my-project
>npm install alchemy-sdk
  1. Import and configure the Alchemy SDK with your API key and choice of network.
javascript
1// Importing the Alchemy SDK
2const { Network, Alchemy } = require('alchemy-sdk');
3
4// Configuring the Alchemy SDK
5const settings = {
6 apiKey: 'demo', // Replace with your Alchemy API Key.
7 network: Network.ETH_MAINNET, // Replace with your network.
8};
9
10// Creating an instance to make requests
11const alchemy = new Alchemy(settings);
  1. Start making requests to the blockchain:
javascript
1// getting the current block number and logging to the console
2alchemy.core.getBlockNumber().then(console.log);
  1. Here’s the complete code:
javascript
1// Importing the Alchemy SDK
2const { Network, Alchemy } = require("alchemy-sdk");
3
4// Configuring the Alchemy SDK
5const settings = {
6 apiKey: "demo", // Replace with your Alchemy API Key.
7 network: Network.ETH_MAINNET, // Replace with your network.
8};
9
10// Creating an instance to make requests
11const alchemy = new Alchemy(settings);
12
13// getting the current block number and logging to the console
14alchemy.core.getBlockNumber().then(console.log);

The Alchemy SDK automatically handles retries for you, so you don’t need to worry about implementing retry logic.

Option 2: Exponential Backoff

Exponential backoff is a standard error-handling strategy for network applications. It is a similar solution to retries, however, instead of waiting random intervals, an exponential backoff algorithm retries requests exponentially, increasing the waiting time between retries up to a maximum backoff time.

Here is an example of an exponential backoff algorithm:

  1. Make a request.
  2. If the request fails, wait 1 + random_number_milliseconds seconds and retry the request.
  3. If the request fails, wait 2 + random_number_milliseconds seconds and retry the request.
  4. If the request fails, wait 4 + random_number_milliseconds seconds and retry the request.
  5. And so on, up to a maximum_backoff time…
  6. Continue waiting and retrying up to some maximum number of retries, but do not increase the wait period between retries.

Where:

  • The wait time is min(((2^n)+random_number_milliseconds), maximum_backoff), with n incremented by 1 for each iteration (request).
  • random_number_milliseconds is a random number of milliseconds less than or equal to 1000. This helps to avoid cases in which many clients are synchronized by some situation and all retry at once, sending requests in synchronized waves. The value of random_number_milliseconds is recalculated after each retry request.
  • maximum_backoff is typically 32 or 64 seconds. The appropriate value depends on the use case.
  • The client can continue retrying after it has reached the maximum_backoff time. Retries after this point do not need to continue increasing backoff time. For example, suppose a client uses a maximum_backoff time of 64 seconds. After reaching this value, the client can retry every 64 seconds. At some point, clients should be prevented from retrying indefinitely.

To implement exponential backoff in your Alchemy application, you can use a library such as retry or async-retry for handling retries in a more structured and scalable way.

Here’s an example implementation of exponential backoff using the async-retry library in a Node.js application where we call the eth_blockNumber API using Alchemy:

javascript
1// Setup: npm install [email protected] | npm install async-retry
2
3// Import required modules
4const fetch = require("node-fetch");
5const retry = require("async-retry");
6
7// Set your API key
8const apiKey = "demo"; // Replace with your Alchemy API key
9
10// Set the endpoint and request options
11const url = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}`;
12const options = {
13 method: "POST",
14 headers: { accept: "application/json", "content-type": "application/json" },
15 body: JSON.stringify({ id: 1, jsonrpc: "2.0", method: "eth_blockNumber" }),
16};
17
18// Create a function to fetch with retries
19const fetchWithRetries = async () => {
20 const result = await retry(
21 async () => {
22 // Make the API request
23 const response = await fetch(url, options);
24
25 // Parse the response JSON
26 let json = await response.json();
27
28 // If we receive a 429 error (Too Many Requests), log an error and retry
29 if (json.error && json.error.code === 429) {
30 console.error("HTTP error 429: Too Many Requests, retrying...");
31 throw new Error("HTTP error 429: Too Many Requests, retrying...");
32 }
33
34 // Otherwise, return the response JSON
35 return json;
36 },
37 {
38 retries: 5, // Number of retries before giving up
39 factor: 2, // Exponential factor
40 minTimeout: 1000, // Minimum wait time before retrying
41 maxTimeout: 60000, // Maximum wait time before retrying
42 randomize: true, // Randomize the wait time
43 }
44 );
45
46 // Return the result
47 return result;
48};
49
50// Call the fetchWithRetries function and log the result, or any errors
51fetchWithRetries()
52 .then((json) => console.log(json))
53 .catch((err) => console.error("error:" + err));

In this example, we define a new function called fetchWithRetries that uses the async-retry library to retry the fetch request with exponential backoff. The retry function takes two arguments:

  1. An async function that performs the fetch request and returns a response object or throws an error.
  2. An options object that specifies the retry behavior. We set the number of retries to 5, the exponential factor to 2, and the minimum and maximum wait times to 1 second and 60 seconds, respectively.

Finally, we call the fetchWithRetries function and log the result or the error to the console.

Option 3: Simple Retries

If exponential backoff poses a challenge to you, a simple retry solution is to wait a random interval between 1000 and 1250 milliseconds after receiving a 429 response and sending the request again, up to some maximum number of attempts you are willing to wait.

Here’s an example implementation of simple retries in a node.js application where we call the eth_blocknumber API using Alchemy:

javascript
1// Setup: npm install [email protected]
2
3// Import required modules
4const fetch = require("node-fetch");
5
6// Set your API key
7const apiKey = "demo"; // Replace with your Alchemy API key
8
9// Set the endpoint and request options
10const url = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}`;
11const options = {
12 method: "POST",
13 headers: { accept: "application/json", "content-type": "application/json" },
14 body: JSON.stringify({ id: 1, jsonrpc: "2.0", method: "eth_blockNumber" }),
15};
16
17const maxRetries = 5; // Maximum number of retries before giving up
18let retries = 0; // Current number of retries
19
20// Create a function to make the request
21function makeRequest() {
22 fetch(url, options)
23 .then((res) => {
24 if (res.status === 429 && retries < maxRetries) {
25 // If we receive a 429 response, wait for a random amount of time and try again
26 const retryAfter = Math.floor(Math.random() * 251) + 1000; // Generate a random wait time between 1000ms and 1250ms
27 console.log(`Received 429 response, retrying after ${retryAfter} ms`);
28 retries++;
29 setTimeout(() => {
30 makeRequest(); // Try the request again after the wait time has elapsed
31 }, retryAfter);
32 } else if (res.ok) {
33 return res.json(); // If the response is successful, return the JSON data
34 } else {
35 throw new Error(`Received ${res.status} status code`); // If the response is not successful, throw an error
36 }
37 })
38 .then((json) => console.log(json)) // Log the JSON data if there were no errors
39 .catch((err) => {
40 if (retries < maxRetries) {
41 console.error(`Error: ${err.message}, retrying...`);
42 retries++;
43 makeRequest(); // Try the request again
44 } else {
45 console.error(`Max retries reached, exiting: ${err.message}`);
46 }
47 });
48}
49
50makeRequest(); // Call the function to make the initial request.
  • In this example, we define a maxRetries constant to limit the number of retries we’re willing to wait. We also define a retries variable to keep track of how many times we’ve retried so far.

  • We then define the makeRequest() function, which is responsible for making the API request. We use the fetch function to send the request with the specified url and options.

  • We then check the response status: if it’s a 429 (Too Many Requests) response and we haven’t reached the maxRetries limit, we wait a random interval between 1000 and 1250 milliseconds before calling makeRequest() again. Otherwise, if the response is OK, we parse the JSON response using res.json() and log it to the console. If the response status is anything else, we throw an error.

  • If an error is caught, we check if we’ve reached the maxRetries limit. If we haven’t, we log an error message and call makeRequest() again after waiting a random interval between 1000 and 1250 milliseconds. If we have reached the maxRetries limit, we log an error message and exit the function.

Finally, we call makeRequest() to start the process.

Option 4: Retry-After

If you’re using HTTP instead of WebSockets, you might come across a ‘Retry-After’ header in the HTTP response. This header serves as the duration you should wait before initiating a subsequent request. Despite the utility of the ‘Retry-After’ header, we continue to advise the use of exponential backoff. This is because the ‘Retry-After’ header only provides a fixed delay duration, while exponential backoff offers a more adaptable delay scheme. By adjusting the delay durations, exponential backoff can effectively prevent a server from being swamped with a high volume of requests in a short time frame.

Here’s an example implementation of “Retry-After” in a node.js application where we call the eth_blocknumber API using Alchemy:

go
1// Setup: npm install [email protected]
2
3// Import required modules
4const fetch = require("node-fetch");
5
6// Set your API key
7const apiKey = "demo"; // Replace with your Alchemy API key
8
9// Set the endpoint and request options
10const url = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}`;
11const options = {
12 method: "POST",
13 headers: {
14 accept: "application/json",
15 "content-type": "application/json",
16 },
17 body: JSON.stringify({ id: 1, jsonrpc: "2.0", method: "eth_blockNumber" }),
18};
19
20const maxRetries = 5; // maximum number of retries
21let retries = 0; // number of retries
22
23// Create a function to fetch with retries
24function makeRequest() {
25 fetch(url, options)
26 .then((res) => {
27 if (res.status === 429 && retries < maxRetries) {
28 // check for 429 status code and if max retries not reached
29 const retryAfter = res.headers.get("Retry-After"); // get the value of Retry-After header in the response
30 if (retryAfter) {
31 // if Retry-After header is present
32 const retryAfterMs = parseInt(retryAfter) * 1000; // convert Retry-After value to milliseconds
33 console.log(
34 `Received 429 response, retrying after ${retryAfter} seconds`
35 );
36 retries++;
37 setTimeout(() => {
38 makeRequest(); // call the same function after the delay specified in Retry-After header
39 }, retryAfterMs);
40 } else {
41 // if Retry-After header is not present
42 const retryAfterMs = Math.floor(Math.random() * 251) + 1000; // generate a random delay between 1 and 250 milliseconds
43 console.log(
44 `Received 429 response, retrying after ${retryAfterMs} ms`
45 );
46 retries++;
47 setTimeout(() => {
48 makeRequest(); // call the same function after the random delay
49 }, retryAfterMs);
50 }
51 } else if (res.ok) {
52 // if response is successful
53 return res.json(); // parse the response as JSON
54 } else {
55 throw new Error(`Received ${res.status} status code`); // throw an error for any other status code
56 }
57 })
58 .then((json) => console.log(json)) // log the JSON response
59 .catch((err) => {
60 if (retries < maxRetries) {
61 // if max retries not reached
62 console.error(`Error: ${err.message}, retrying...`);
63 retries++;
64 makeRequest(); // call the same function again
65 } else {
66 // if max retries reached
67 console.error(`Max retries reached, exiting: ${err.message}`);
68 }
69 });
70}
71
72makeRequest(); // call the makeRequest function to start the retry loop
  • The code starts by defining the API endpoint URL and the request options. It then sets up a function makeRequest() that uses fetch() to make a POST request to the API.
  • If the response status code is 429 (Too Many Requests), the code checks for a Retry-After header in the response.
  • If the header is present, the code retries the request after the number of seconds specified in the header.
  • If the header is not present, the code generates a random retry time between 1 and 250ms and retries the request after that time.
  • If the response status code is not 429 and is not OK, the code throws an error.
  • If the response is OK, the code returns the response JSON. If there is an error, the code catches the error and retries the request if the number of retries is less than the maximum number of retries.
  • If the number of retries is equal to the maximum number of retries, the code logs an error message and exits.

Conclusion

In conclusion, retries are an important error-handling strategy for network applications that can help improve application reliability and handle errors. In this tutorial, we discussed 4 ways in which we can implement retries namely: Exponential-Backoff, Retry-After, Simple Retries and Alchemy SDK.

By implementing retries in your Alchemy application, you can help ensure that your application can handle errors and continue to function reliably even in the face of unexpected errors and network disruptions.