packages/utils/request-manager/src/CommonRequestManager.ts (164 lines of code) (raw):
import { RequestManager, ConfigType, RequestPending } from './types'
export class CommonRequestManager<RequestArgsType = { url: string; requestParams?: any }>
implements RequestManager<RequestArgsType>
{
readonly config: ConfigType
protected _requestCacheMap: Map<string, RequestPending> = new Map()
protected _dataCacheMap: Map<string, any> = new Map()
constructor(config: ConfigType) {
if (!config.dataType) {
throw new Error(`CommonRequestManager - Invalid config param: dataType`)
}
this.config = config
}
request(requestArg: RequestArgsType): RequestPending {
const cacheKey = this.getCacheKey(requestArg)
if (typeof cacheKey !== 'string') {
throw new Error('CommonRequestManager - CacheKey must be string')
}
const cachedData = this._dataCacheMap.get(cacheKey)
if (cachedData !== undefined) {
return { promise: Promise.resolve(cachedData) }
}
const cachedPromise = this._requestCacheMap.get(cacheKey)
if (cachedPromise) {
return cachedPromise
}
if (this.config.fetcher) {
const fetcherPending = this.config.fetcher(requestArg)
if (!fetcherPending.promise) {
throw new Error(
`CommonRequestManager - Invalid custom fetcher return type: should be: { promise, abort? }`
)
}
return fetcherPending
}
// abort signal prep
const abortController = AbortController ? new AbortController() : undefined
const signal = abortController ? abortController.signal : undefined
const promise = new Promise<any>((resolve, reject) => {
this.fetchDataDefault(requestArg, signal)
.then((result) => {
this._dataCacheMap.set(cacheKey, result)
this._requestCacheMap.delete(cacheKey)
resolve(result)
})
.catch((e) => {
this._dataCacheMap.delete(cacheKey)
this._requestCacheMap.delete(cacheKey)
reject(e)
})
})
const abort = () => {
if (abortController) {
abortController.abort()
return { success: true }
}
return { success: false }
}
const requestPending = {
promise,
abort,
}
this._requestCacheMap.set(cacheKey, requestPending)
return requestPending
}
getCacheKey(requestArg: RequestArgsType) {
let cacheKey = ''
for (const key in requestArg) {
if (Object.prototype.hasOwnProperty.call(requestArg, key)) {
const value = requestArg[key]
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint') {
cacheKey += value.toString() + '|'
} else if (typeof value === 'function') {
continue
} else if (typeof value === 'object') {
cacheKey += JSON.stringify(value) + '|'
} else {
cacheKey += `${value}` + '|'
}
}
}
return cacheKey
}
dispose() {
this._requestCacheMap.clear()
this._dataCacheMap.clear()
}
protected fetchDataDefault(requestArg: RequestArgsType, abortSignal?: AbortSignal): Promise<any> {
const args = requestArg as any
const url = typeof requestArg === 'string' ? args : args.url
const requestParams =
typeof requestArg === 'string' ? undefined : args.requestParams || args.params
if (!url) {
console.error('CommonRequestManager - Invalid request url param. ')
return Promise.reject()
}
return new Promise((resolve, reject) => {
fetch(url, {
...requestParams,
signal: abortSignal,
})
.then((res) => {
if (!res.ok) {
reject(res)
return
}
switch (this.config.dataType) {
case 'auto': {
const data = this.getDataFromResponse(res)
if (data) {
resolve(data)
} else {
reject(new Error('Unknown Response Content-Type'))
}
break
}
case 'arraybuffer': {
res.arrayBuffer().then(resolve).catch(reject)
break
}
case 'json': {
res.json().then(resolve).catch(reject)
break
}
case 'text': {
res.text().then(resolve).catch(reject)
break
}
default: {
resolve(res)
}
}
})
.catch(reject)
})
// requestParams = requestParams || {}
// return new Promise((resolve, reject) => {
// const xhr = new XMLHttpRequest()
// xhr.responseType = this.config.dataType
// const listener = (e) => {
// const status = xhr.status
// if (status === 200) {
// resolve(xhr.response)
// } else if (status === 400) {
// reject(new Error('Request failed because the status is 404'))
// } else {
// reject(new Error('Request failed because the status is not 200'))
// }
// }
// xhr.addEventListener('loadend', listener)
// xhr.addEventListener('error', (e) => {
// reject(new Error('Request failed'))
// })
// xhr.open(requestParams.method || 'GET', url)
// xhr.send()
// })
}
protected getDataFromResponse(res: Response) {
const contentType = res.headers.get('Content-Type')
if (!contentType) {
console.error(
'CommonRequestManager - No Content-Type was found in response headers, use .json() by default'
)
return res.json()
}
const type = contentType.split(';')[0]
switch (type) {
case 'application/json': {
return res.json()
}
case 'application/octet-stream': {
return res.arrayBuffer()
}
case 'application/x-protobuf': {
return res.arrayBuffer()
}
case 'text/plain': {
return res.text()
}
default: {
console.error(`CommonRequestManager - Unknown Response Content-Type: ${contentType}`)
return
}
}
}
}