feat: ensure timeout and request are properly cleared

This commit is contained in:
getlarge 2022-02-15 14:10:49 +01:00
parent c5fe1346b9
commit 8c0c72622d
No known key found for this signature in database
GPG Key ID: E4E13243600F9566
1 changed files with 39 additions and 18 deletions

View File

@ -9,14 +9,14 @@ import { vsprintf } from 'sprintf-js'
import formatText from './format_text' import formatText from './format_text'
import stringifyAsQueryParam from './stringify_as_query_param' import stringifyAsQueryParam from './stringify_as_query_param'
const fetch = fetchPonyfill(Promise) const fetch = fetchPonyfill({ Promise })
export function ResponseError(message, status, requestURI) { export function ResponseError(message, status, requestURI) {
this.name = 'ResponseError' this.name = 'ResponseError'
this.message = message this.message = message
this.status = status this.status = status
this.requestURI = requestURI this.requestURI = requestURI
this.stack = (new Error()).stack this.stack = new Error().stack
} }
ResponseError.prototype = new Error() ResponseError.prototype = new Error()
@ -26,17 +26,27 @@ ResponseError.prototype = new Error()
* Timeout function following https://github.com/github/fetch/issues/175#issuecomment-284787564 * Timeout function following https://github.com/github/fetch/issues/175#issuecomment-284787564
* @param {integer} obj Source object * @param {integer} obj Source object
* @param {Promise} filter Array of key names to select or function to invoke per iteration * @param {Promise} filter Array of key names to select or function to invoke per iteration
* @param {AbortController} controller AbortController instance bound to fetch
* @return {Object} TimeoutError if the time was consumed, otherwise the Promise will be resolved * @return {Object} TimeoutError if the time was consumed, otherwise the Promise will be resolved
*/ */
function timeout(ms, promise) { function timeout(ms, promise, controller) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => { const nodeTimeout = setTimeout(() => {
controller.abort()
const errorObject = { const errorObject = {
message: 'TimeoutError' message: 'TimeoutError',
} }
reject(new Error(errorObject)) reject(new Error(errorObject))
}, ms) }, ms)
promise.then(resolve, reject) promise
.then((res) => {
clearTimeout(nodeTimeout)
resolve(res)
})
.catch((err) => {
clearTimeout(nodeTimeout)
reject(err)
})
}) })
} }
@ -88,25 +98,30 @@ function handleResponse(res) {
* @return {Promise} If requestTimeout the timeout function will be called. Otherwise resolve the * @return {Promise} If requestTimeout the timeout function will be called. Otherwise resolve the
* Promise with the handleResponse function * Promise with the handleResponse function
*/ */
export default function baseRequest(url, { export default function baseRequest(
jsonBody, url,
query, {
urlTemplateSpec, jsonBody, query, urlTemplateSpec, ...fetchConfig
...fetchConfig } = {},
} = {}, requestTimeout) { requestTimeout = 0
) {
let expandedUrl = url let expandedUrl = url
if (urlTemplateSpec != null) { if (urlTemplateSpec != null) {
if (Array.isArray(urlTemplateSpec) && urlTemplateSpec.length) { if (Array.isArray(urlTemplateSpec) && urlTemplateSpec.length) {
// Use vsprintf for the array call signature // Use vsprintf for the array call signature
expandedUrl = vsprintf(url, urlTemplateSpec) expandedUrl = vsprintf(url, urlTemplateSpec)
} else if (urlTemplateSpec && } else if (
urlTemplateSpec &&
typeof urlTemplateSpec === 'object' && typeof urlTemplateSpec === 'object' &&
Object.keys(urlTemplateSpec).length) { Object.keys(urlTemplateSpec).length
) {
expandedUrl = formatText(url, urlTemplateSpec) expandedUrl = formatText(url, urlTemplateSpec)
} else if (process.env.NODE_ENV !== 'production') { } else if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn('Supplied urlTemplateSpec was not an array or object. Ignoring...') console.warn(
'Supplied urlTemplateSpec was not an array or object. Ignoring...'
)
} }
} }
@ -124,11 +139,17 @@ export default function baseRequest(url, {
if (jsonBody != null) { if (jsonBody != null) {
fetchConfig.body = JSON.stringify(jsonBody) fetchConfig.body = JSON.stringify(jsonBody)
} }
if (requestTimeout) { if (requestTimeout) {
return timeout(requestTimeout, fetch.fetch(expandedUrl, fetchConfig)) const controller = new AbortController()
const { signal } = controller
return timeout(
requestTimeout,
fetch.fetch(expandedUrl, { ...fetchConfig, signal }),
controller
)
.then(handleResponse) .then(handleResponse)
} else { } else {
return fetch.fetch(expandedUrl, fetchConfig) return fetch.fetch(expandedUrl, fetchConfig).then(handleResponse)
.then(handleResponse)
} }
} }