Skip to content

Commit

Permalink
Merge pull request #4228 from owid/retry-user-fetches
Browse files Browse the repository at this point in the history
✨ add automatic retry to client fetch requests
  • Loading branch information
danyx23 authored Dec 1, 2024
2 parents 9548c2f + a570ac6 commit af57fc2
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 22 deletions.
3 changes: 2 additions & 1 deletion packages/@ourworldindata/explorer/src/ExplorerProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
SerializedGridProgram,
trimObject,
merge,
fetchWithRetry,
} from "@ourworldindata/utils"
import {
CellDef,
Expand Down Expand Up @@ -421,7 +422,7 @@ export class ExplorerProgram extends GridProgram {
*/
private static tableDataLoader = new PromiseCache(
async (url: string): Promise<CoreTableInputOption> => {
const response = await fetch(url)
const response = await fetchWithRetry(url)
if (!response.ok) throw new Error(response.statusText)
const tableInput: CoreTableInputOption = url.endsWith(".json")
? await response.json()
Expand Down
7 changes: 5 additions & 2 deletions packages/@ourworldindata/grapher/src/core/loadVariable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { OwidVariableDataMetadataDimensions } from "@ourworldindata/types"
import { fetchWithRetry } from "@ourworldindata/utils"

export const getVariableDataRoute = (
dataApiUrl: string,
Expand Down Expand Up @@ -28,8 +29,10 @@ export async function loadVariableDataAndMetadata(
variableId: number,
dataApiUrl: string
): Promise<OwidVariableDataMetadataDimensions> {
const dataPromise = fetch(getVariableDataRoute(dataApiUrl, variableId))
const metadataPromise = fetch(
const dataPromise = fetchWithRetry(
getVariableDataRoute(dataApiUrl, variableId)
)
const metadataPromise = fetchWithRetry(
getVariableMetadataRoute(dataApiUrl, variableId)
)
const [dataResponse, metadataResponse] = await Promise.all([
Expand Down
31 changes: 24 additions & 7 deletions packages/@ourworldindata/utils/src/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ export const trimObject = <Obj>(
}

export const fetchText = async (url: string): Promise<string> => {
return await fetch(url).then((res) => {
return await fetchWithRetry(url).then((res) => {
if (!res.ok)
throw new Error(`Fetch failed: ${res.status} ${res.statusText}`)
return res.text()
Expand All @@ -593,7 +593,7 @@ export const fetchText = async (url: string): Promise<string> => {
const _getUserCountryInformation = async (): Promise<
UserCountryInformation | undefined
> =>
await fetch("/detect-country")
await fetchWithRetry("/detect-country")
.then((res) => res.json())
.then((res) => res.country)
.catch(() => undefined)
Expand Down Expand Up @@ -776,17 +776,34 @@ export const getYearFromISOStringAndDayOffset = (
export const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms))

interface RetryOptions {
maxRetries?: number
exponentialBackoff?: boolean
initialDelay?: number
}

export async function fetchWithRetry(
url: string,
fetchOptions?: RequestInit,
retryOptions?: RetryOptions
): Promise<Response> {
const defaultRetryOptions: RetryOptions = {
maxRetries: 5,
exponentialBackoff: true,
initialDelay: 250,
}
return retryPromise(
() => fetch(url, fetchOptions),
retryOptions ?? defaultRetryOptions
)
}
export async function retryPromise<T>(
promiseGetter: () => Promise<T>,
{
maxRetries = 3,
exponentialBackoff = false,
initialDelay = 200,
}: {
maxRetries?: number
exponentialBackoff?: boolean
initialDelay?: number
} = {}
}: RetryOptions = {}
): Promise<T> {
let retried = 0
let lastError
Expand Down
1 change: 1 addition & 0 deletions packages/@ourworldindata/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export {
moveArrayItemToIndex,
getIndexableKeys,
retryPromise,
fetchWithRetry,
getOwidGdocFromJSON,
formatDate,
canWriteToClipboard,
Expand Down
4 changes: 2 additions & 2 deletions site/detailsOnDemand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Instance } from "tippy.js"
import { BAKED_BASE_URL } from "../settings/clientSettings.js"
import { renderToStaticMarkup } from "react-dom/server.js"
import { ArticleBlocks } from "./gdocs/components/ArticleBlocks.js"
import { DetailDictionary } from "@ourworldindata/utils"
import { DetailDictionary, fetchWithRetry } from "@ourworldindata/utils"
import { SiteAnalytics } from "./SiteAnalytics.js"

type Tippyfied<E> = E & {
Expand All @@ -20,7 +20,7 @@ declare global {
const siteAnalytics = new SiteAnalytics()

export async function runDetailsOnDemand() {
window.details = await fetch(`${BAKED_BASE_URL}/dods.json`, {
window.details = await fetchWithRetry(`${BAKED_BASE_URL}/dods.json`, {
method: "GET",
credentials: "same-origin",
headers: {
Expand Down
11 changes: 6 additions & 5 deletions site/multiDim/MultiDimDataPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
multiDimStateToQueryStr,
merge,
omit,
fetchWithRetry,
} from "@ourworldindata/utils"
import cx from "classnames"
import { DebugProvider } from "../gdocs/DebugContext.js"
Expand Down Expand Up @@ -101,16 +102,16 @@ const getDatapageDataV2 = async (

const cachedGetVariableMetadata = memoize(
(variableId: number): Promise<OwidVariableWithSourceAndDimension> =>
fetch(getVariableMetadataRoute(DATA_API_URL, variableId)).then((resp) =>
resp.json()
fetchWithRetry(getVariableMetadataRoute(DATA_API_URL, variableId)).then(
(resp) => resp.json()
)
)

const cachedGetGrapherConfigByUuid = memoize(
(grapherConfigUuid: string): Promise<GrapherInterface> =>
fetch(`/grapher/by-uuid/${grapherConfigUuid}.config.json`).then(
(resp) => resp.json()
)
fetchWithRetry(
`/grapher/by-uuid/${grapherConfigUuid}.config.json`
).then((resp) => resp.json())
)

const useTitleFragments = (config: MultiDimDataPageConfig) => {
Expand Down
9 changes: 5 additions & 4 deletions site/multiembedder/MultiEmbedder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
merge,
MultiDimDataPageConfig,
extractMultiDimChoicesFromQueryStr,
fetchWithRetry,
} from "@ourworldindata/utils"
import { action } from "mobx"
import React from "react"
Expand Down Expand Up @@ -178,8 +179,8 @@ class MultiEmbedder {
let configUrl
if (isMultiDim) {
const mdimConfigUrl = `${MULTI_DIM_DYNAMIC_CONFIG_URL}/${slug}.json`
const mdimJsonConfig = await fetch(mdimConfigUrl).then((res) =>
res.json()
const mdimJsonConfig = await fetchWithRetry(mdimConfigUrl).then(
(res) => res.json()
)
const mdimConfig =
MultiDimDataPageConfig.fromObject(mdimJsonConfig)
Expand All @@ -199,8 +200,8 @@ class MultiEmbedder {
} else {
configUrl = `${GRAPHER_DYNAMIC_CONFIG_URL}/${slug}.config.json`
}
const grapherPageConfig = await fetch(configUrl).then((res) =>
res.json()
const grapherPageConfig = await fetchWithRetry(configUrl).then(
(res) => res.json()
)

const figureConfigAttr = figure.getAttribute(
Expand Down
3 changes: 2 additions & 1 deletion site/search/evaluateSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Simulate searches against our Algolia index and evaluate the results.
*/

import { fetchWithRetry } from "@ourworldindata/utils"
import {
ALGOLIA_ID,
ALGOLIA_SEARCH_KEY,
Expand Down Expand Up @@ -97,7 +98,7 @@ const getClient = (): any => {

const fetchQueryDataset = async (name: string): Promise<QueryDataset> => {
const url: string = `${SEARCH_EVAL_URL}/${name}`
const resp = await fetch(url)
const resp = await fetchWithRetry(url)
const jsonData = await resp.json()
return { name, queries: jsonData }
}
Expand Down

0 comments on commit af57fc2

Please sign in to comment.