diff --git a/packages/@uppy/utils/src/fetcher.ts b/packages/@uppy/utils/src/fetcher.ts index d96a9f1b64..661ff1bfc2 100644 --- a/packages/@uppy/utils/src/fetcher.ts +++ b/packages/@uppy/utils/src/fetcher.ts @@ -84,6 +84,16 @@ export function fetcher( // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { const xhr = new XMLHttpRequest() + const onError = (error: Error) => { + if (shouldRetry(xhr) && retryCount < retries) { + setTimeout(() => { + requestWithRetry(retryCount + 1).then(resolve, reject) + }, delay(retryCount)) + } else { + timer.done() + reject(error) + } + } xhr.open(method, url, true) xhr.withCredentials = withCredentials @@ -99,7 +109,15 @@ export function fetcher( }) xhr.onload = async () => { - await onAfterResponse(xhr, retryCount) + try { + await onAfterResponse(xhr, retryCount) + } catch (err) { + // This is important as we need to emit the xhr + // over the upload-error event. + err.request = xhr + onError(err) + return + } if (xhr.status >= 200 && xhr.status < 300) { timer.done() @@ -114,16 +132,7 @@ export function fetcher( } } - xhr.onerror = () => { - if (shouldRetry(xhr) && retryCount < retries) { - setTimeout(() => { - requestWithRetry(retryCount + 1).then(resolve, reject) - }, delay(retryCount)) - } else { - timer.done() - reject(new NetworkError(xhr.statusText, xhr)) - } - } + xhr.onerror = () => onError(new NetworkError(xhr.statusText, xhr)) xhr.upload.onprogress = (event: ProgressEvent) => { timer.progress() diff --git a/packages/@uppy/xhr-upload/src/index.test.ts b/packages/@uppy/xhr-upload/src/index.test.ts index fc461c3ba4..1818b2a9a3 100644 --- a/packages/@uppy/xhr-upload/src/index.test.ts +++ b/packages/@uppy/xhr-upload/src/index.test.ts @@ -79,7 +79,14 @@ describe('XHRUpload', () => { id: 'XHRUpload', endpoint: 'https://fake-endpoint.uppy.io', shouldRetry, + async onAfterResponse(xhr) { + if (xhr.status === 400) { + // We want to test that we can define our own error message + throw new Error(JSON.parse(xhr.responseText).message) + } + }, }) + const id = core.addFile({ type: 'image/png', source: 'test', @@ -95,7 +102,7 @@ describe('XHRUpload', () => { await Promise.all([ core.upload(), - event.then(([file, , response]) => { + event.then(([file, error, response]) => { const newFile = core.getFile(id) // error and response are set inside upload-error in core. // When we subscribe to upload-error it is emitted before @@ -107,6 +114,7 @@ describe('XHRUpload', () => { // might have changed in the meantime expect(file).toEqual(newFile) expect(response).toBeInstanceOf(XMLHttpRequest) + expect(error.message).toEqual('Oh no') }), ]) diff --git a/packages/@uppy/xhr-upload/src/index.ts b/packages/@uppy/xhr-upload/src/index.ts index bad7a83c68..d9f3fb9361 100644 --- a/packages/@uppy/xhr-upload/src/index.ts +++ b/packages/@uppy/xhr-upload/src/index.ts @@ -82,7 +82,7 @@ declare module '@uppy/core' { } function buildResponseError( - xhr: XMLHttpRequest, + xhr?: XMLHttpRequest, err?: string | Error | NetworkError, ) { let error = err @@ -255,17 +255,15 @@ export default class XHRUpload< if (error.name === 'AbortError') { return undefined } - if (error instanceof NetworkError) { - const request = error.request! - - for (const file of files) { - this.uppy.emit( - 'upload-error', - this.uppy.getFile(file.id), - buildResponseError(request, error), - request, - ) - } + const request = error.request as XMLHttpRequest | undefined + + for (const file of files) { + this.uppy.emit( + 'upload-error', + this.uppy.getFile(file.id), + buildResponseError(request, error), + request, + ) } throw error