Skip to content
This repository has been archived by the owner on Jan 15, 2021. It is now read-only.

Commit

Permalink
Add timeout for fetching tokens (#1644)
Browse files Browse the repository at this point in the history
* Add timeout for fetching tokens

* Improve timeout util (#1645)

* simplify type without casting

* default delay() return to void

* simplify timeout function logic

* improve timeout types

* use new timeout with automatic rejected Promise detection

* construct cacheKey once only

* throw proper Error

Co-authored-by: Velenir <[email protected]>
  • Loading branch information
anxolin and Velenir authored Nov 30, 2020
1 parent 2e0c015 commit 04fafbb
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Network } from 'types'
import { DisabledTokensMaps, TokenOverride, AddressToOverrideMap } from 'types/config'

export const BATCH_TIME_IN_MS = BATCH_TIME * 1000
export const DEFAULT_TIMEOUT = 5000

export const ZERO_BIG_NUMBER = new BigNumber(0)
export const ONE_BIG_NUMBER = new BigNumber(1)
Expand Down
48 changes: 30 additions & 18 deletions src/hooks/useTokenBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { erc20Api, depositApi } from 'api'
import useSafeState from './useSafeState'
import { useWalletConnection } from './useWalletConnection'

import { formatSmart, logDebug, isTokenEnabled } from 'utils'
import { formatSmart, logDebug, isTokenEnabled, timeout, notEmpty } from 'utils'
import { TokenBalanceDetails, TokenDetails } from 'types'
import { WalletInfo } from 'api/wallet/WalletApi'
import { PendingFlux } from 'api/deposit/DepositApi'
Expand All @@ -34,6 +34,7 @@ async function fetchBalancesForToken(
networkId: number,
): Promise<TokenBalanceDetails> {
const tokenAddress = token.address

const [
exchangeBalance,
pendingDeposit,
Expand Down Expand Up @@ -83,29 +84,40 @@ async function _getBalances(walletInfo: WalletInfo, tokens: TokenDetails[]): Pro
const contractAddress = depositApi.getContractAddress(networkId)
assert(contractAddress, 'No valid contract address found. Stopping.')

const balancePromises: Promise<TokenBalanceDetails | null>[] = tokens.map((token) =>
fetchBalancesForToken(token, userAddress, contractAddress, networkId)
.then((balance) => {
const cacheKey = constructCacheKey({ token, userAddress, contractAddress, networkId })
const balancePromises: Promise<TokenBalanceDetails | null>[] = tokens.map((token) => {
const cacheKey = constructCacheKey({ token, userAddress, contractAddress, networkId })

// timoutPromise == Promise<never>, correctly determined to always throw
const timeoutPromise = timeout({
timeoutErrorMsg: 'Timeout fetching balances for ' + token.address,
})

const fetchBalancesPromise = fetchBalancesForToken(token, userAddress, contractAddress, networkId).then(
(balance) => {
balanceCache[cacheKey] = balance
return balance
})
.catch((e) => {
console.error('[useTokenBalances] Error for', token, userAddress, contractAddress, e)
},
)

const cacheKey = constructCacheKey({ token, userAddress, contractAddress, networkId })
// balancePromise == Promise<TokenBalanceDetails | never> == Promise<TokenBalanceDetails> or throws
const balancePromise = Promise.race([fetchBalancesPromise, timeoutPromise])

const cachedValue = balanceCache[cacheKey]
if (cachedValue) {
logDebug('Using cached value for', token, userAddress, contractAddress)
return cachedValue
}
return balancePromise.catch((e) => {
console.error('[useTokenBalances] Error for', token, userAddress, contractAddress, e)
const cachedValue = balanceCache[cacheKey]
if (cachedValue) {
logDebug('Using cached value for', token, userAddress, contractAddress)
return cachedValue
}

return null
})
})

return null
}),
)
const balances = await Promise.all(balancePromises)
return balances.filter(Boolean) as TokenBalanceDetails[]

// TODO: Would be better to show the errored tokens in error state
return balances.filter(notEmpty)
}

export const useTokenBalances = (passOnParams: Partial<UseTokenListParams> = {}): UseBalanceResult => {
Expand Down
23 changes: 21 additions & 2 deletions src/utils/miscellaneous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TokenDetails, Unpromise } from 'types'
import { AssertionError } from 'assert'
import { AuctionElement, Trade, Order } from 'api/exchange/ExchangeApi'
import { batchIdToDate } from './time'
import { ORDER_FILLED_FACTOR, MINIMUM_ALLOWANCE_DECIMALS } from 'const'
import { ORDER_FILLED_FACTOR, MINIMUM_ALLOWANCE_DECIMALS, DEFAULT_TIMEOUT } from 'const'
import { TEN, ZERO } from '@gnosis.pm/dex-js'

export function assertNonNull<T>(val: T, message: string): asserts val is NonNullable<T> {
Expand Down Expand Up @@ -72,7 +72,8 @@ export function getToken<T extends TokenDetails, K extends keyof T>(
return token
}

export const delay = <T>(ms = 100, result?: T): Promise<T> => new Promise((resolve) => setTimeout(resolve, ms, result))
export const delay = <T = void>(ms = 100, result?: T): Promise<T> =>
new Promise((resolve) => setTimeout(resolve, ms, result))

/**
* Uses images from https://github.com/trustwallet/tokens
Expand Down Expand Up @@ -210,3 +211,21 @@ export function notEmpty<TValue>(value: TValue | null | undefined): value is TVa
}

export const isNonZeroNumber = (value?: string | number): boolean => !!value && !!+value

export interface TimeoutParams<T> {
time?: number
result?: T
timeoutErrorMsg?: string
}

export function timeout(params: TimeoutParams<undefined>): Promise<never> // never means function throws
export function timeout<T>(params: TimeoutParams<T extends undefined ? never : T>): Promise<T>
export async function timeout<T>(params: TimeoutParams<T>): Promise<T | never> {
const { time = DEFAULT_TIMEOUT, result, timeoutErrorMsg: timeoutMsg = 'Timeout' } = params

await delay(time)
// provided defined result -- return it
if (result !== undefined) return result
// no defined result -- throw message
throw new Error(timeoutMsg)
}

0 comments on commit 04fafbb

Please sign in to comment.