Skip to content

Commit

Permalink
fix: improve error handling for non-user errors
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Nov 5, 2021
1 parent f8c20d5 commit 6b965a5
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 38 deletions.
4 changes: 3 additions & 1 deletion playground/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { $fetch } from '../src/node'

async function main () {
const r = await $fetch<string>('http://google.com/404')
// const r = await $fetch<string>('http://google.com/404')
const r = await $fetch<string>('http://httpstat.us/500')
// const r = await $fetch<string>('http://httpstat/500')
// eslint-disable-next-line no-console
console.log(r)
}
Expand Down
33 changes: 13 additions & 20 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,20 @@ export class FetchError<T = any> extends Error {
data?: T
}

export function createFetchError<T = any> (request: FetchRequest, response: FetchResponse<T>): FetchError<T> {
const message = `${response.status} ${response.statusText} (${request.toString()})`
const error: FetchError<T> = new FetchError(message)
export function createFetchError<T = any> (request: FetchRequest, error?: Error, response?: FetchResponse<T>): FetchError<T> {
let message = ''
if (request && response) {
message = `${response.status} ${response.statusText} (${request.toString()})`
}
if (error) {
message = `${error.message} (${message})`
}

Object.defineProperty(error, 'request', { get () { return request } })
Object.defineProperty(error, 'response', { get () { return response } })
Object.defineProperty(error, 'data', { get () { return response.data } })
const fetchError: FetchError<T> = new FetchError(message)

const stack = error.stack
Object.defineProperty(error, 'stack', { get () { return normalizeStack(stack) } })
Object.defineProperty(fetchError, 'request', { get () { return request } })
Object.defineProperty(fetchError, 'response', { get () { return response } })
Object.defineProperty(fetchError, 'data', { get () { return response && response.data } })

return error
}

function normalizeStack (stack: string = '') {
return stack.split('\n')
.filter(l =>
!l.includes('createFetchError') &&
!l.includes('at $fetch') &&
!l.includes('at $fetchRaw') &&
!l.includes('processTicksAndRejections')
)
.join('\n')
return fetchError
}
41 changes: 24 additions & 17 deletions src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,40 +46,47 @@ export function setHeader (options: FetchOptions, _key: string, value: string) {
}

export function createFetch ({ fetch }: CreateFetchOptions): $Fetch {
const $fetchRaw: $Fetch['raw'] = async function (request, opts = {}) {
const hasPayload = payloadMethods.includes((opts.method || '').toLowerCase())
function onError (request: FetchRequest, opts: FetchOptions, error?: Error, response?: FetchResponse<any>): Promise<FetchResponse<any>> {
// Retry
if (opts.retry !== false) {
const hasPayload = payloadMethods.includes((opts.method || '').toLowerCase())
const retries = typeof opts.retry === 'number' ? opts.retry : (hasPayload ? 0 : 1)
if (retries > 0) {
return $fetchRaw(request, {
...opts,
retry: retries - 1
})
}
}

// Throw normalized error
const err = createFetchError(request, error, response)
Error.captureStackTrace(err, $fetchRaw)
throw err
}

const $fetchRaw: $Fetch['raw'] = async function $fetchRaw (request, opts = {}) {
if (typeof request === 'string') {
if (opts.baseURL) {
request = joinURL(opts.baseURL, request)
}
if (opts.params) {
request = withQuery(request, opts.params)
}
const hasPayload = payloadMethods.includes((opts.method || '').toLowerCase())
if (opts.body && opts.body.toString() === '[object Object]' && hasPayload) {
opts.body = JSON.stringify(opts.body)
setHeader(opts, 'content-type', 'application/json')
}
}
const response: FetchResponse<any> = await fetch(request, opts as RequestInit)
const response: FetchResponse<any> = await fetch(request, opts as RequestInit).catch(error => onError(request, opts, error, undefined))
const text = await response.text()
const parseFn = opts.parseResponse || destr
response.data = parseFn(text)
if (!response.ok) {
if (opts.retry !== false) {
const retries = typeof opts.retry === 'number' ? opts.retry : (hasPayload ? 0 : 1)
if (retries > 0) {
return $fetchRaw(request, {
...opts,
retry: retries - 1
})
}
}
throw createFetchError(request, response)
}
return response
return response.ok ? response : onError(request, opts, undefined, response)
}

const $fetch = function (request, opts) {
const $fetch = function $fetch (request, opts) {
return $fetchRaw(request, opts).then(r => r.data)
} as $Fetch

Expand Down

0 comments on commit 6b965a5

Please sign in to comment.