protected _respond()

in src/SimpleWebRequest.ts [730:910]


    protected _respond(errorStatusText?: string): void {
        if (this._finishHandled) {
            // Aborted web requests often double-finish due to odd browser behavior, but non-aborted requests shouldn't...
            // Unfortunately, this assertion fires frequently in the Safari browser, presumably due to a non-standard
            // XHR implementation, so we need to comment it out.
            // This also might get hit during browser feature detection process
            // assert(this._aborted || this._timedOut, 'Double-finished XMLHttpRequest');
            return;
        }

        this._finishHandled = true;

        this._removeFromQueue();

        if (this._retryTimer) {
            SimpleWebRequestOptions.clearTimeout(this._retryTimer);
            this._retryTimer = undefined;
        }

        if (this._requestTimeoutTimer) {
            SimpleWebRequestOptions.clearTimeout(this._requestTimeoutTimer);
            this._requestTimeoutTimer = undefined;
        }

        let statusCode = 0;
        let statusText: string|undefined;
        if (this._xhr) {
            try {
                statusCode = this._xhr.status;
                statusText = this._xhr.statusText || errorStatusText;
            } catch (e) {
                // Some browsers error when you try to read status off aborted requests
            }
        } else {
            statusText = errorStatusText || 'Browser Error - Possible CORS or Connectivity Issue';
        }

        let headers: Headers = {};
        let body: any;
        let responseParsingException: Error | undefined;

        // Build the response info
        if (this._xhr) {
            // Parse out headers
            const headerLines = (this._xhr.getAllResponseHeaders() || '').split(/\r?\n/);
            headerLines.forEach(line => {
                if (line.length === 0) {
                    return;
                }

                const index = line.indexOf(':');
                if (index === -1) {
                    headers[line] = '';
                } else {
                    headers[line.substr(0, index).toLowerCase()] = line.substr(index + 1).trim();
                }
            });

            // Some browsers apparently don't set the content-type header in some error conditions from getAllResponseHeaders but do return
            // it from the normal getResponseHeader.  No clue why, but superagent mentions it as well so it's best to just conform.
            if (!headers['content-type']) {
                const check = this._xhr.getResponseHeader('content-type');
                if (check) {
                    headers['content-type'] = check;
                }
            }

            body = this._xhr.response;
            if (headers['content-type'] && isJsonContentType(headers['content-type'])) {
                if (!body || !isObject(body)) {
                    // Response can be null if the responseType does not match what the server actually sends
                    // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType

                    // Only access responseText if responseType is "text" or "", otherwise it will throw
                    // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText
                    if ((this._xhr.responseType === 'text' || this._xhr.responseType === '') && this._xhr.responseText) {
                        try {
                            body = JSON.parse(this._xhr.responseText);
                        } catch (ex) {
                            // If a service returns invalid JSON in a payload, we can end up here - don't crash
                            // responseParsingException flag will indicate that we got response from the server that was corrupted.
                            // This will be manifested as null on receipient side and flag can help in understanding the problem.
                            responseParsingException = ex;
                            console.warn('Failed to parse XHR JSON response');
                        }
                    }
                }
            }
        }

        if (this._xhr && this._xhr.readyState === 4 && ((statusCode >= 200 && statusCode < 300) || statusCode === 304)) {
            // Happy path!
            const resp: WebResponse<TBody, TOptions> = {
                url: this._xhr.responseURL || this._url,
                method: this._action,
                requestOptions: this._options,
                requestHeaders: this._xhrRequestHeaders || {},
                statusCode: statusCode,
                statusText: statusText,
                headers: headers,
                body: body as TBody,
                responseParsingException: responseParsingException,
            };

            this._resolve!!!(resp);
        } else {
            let errResp: WebErrorResponse<TOptions> = {
                url: (this._xhr ? this._xhr.responseURL : undefined) || this._url,
                method: this._action,
                requestOptions: this._options,
                requestHeaders: this._xhrRequestHeaders || {},
                statusCode: statusCode,
                statusText: statusText,
                headers: headers,
                body: body,
                canceled: this._aborted,
                timedOut: this._timedOut,
                responseParsingException: responseParsingException,
            };

            if (this._options.augmentErrorResponse) {
                this._options.augmentErrorResponse(errResp);
            }

            // Policy-adaptable failure
            const handleResponse = this._options.customErrorHandler
                ? this._options.customErrorHandler(this, errResp)
                : DefaultErrorHandler(this, errResp);

            const retry = handleResponse !== ErrorHandlingType.DoNotRetry && (
                (this._options.retries && this._options.retries > 0) ||
                handleResponse === ErrorHandlingType.PauseUntilResumed ||
                handleResponse === ErrorHandlingType.RetryUncountedImmediately ||
                handleResponse === ErrorHandlingType.RetryUncountedWithBackoff);

            if (retry) {
                if (handleResponse === ErrorHandlingType.RetryCountedWithBackoff) {
                    this._options.retries!!!--;
                }

                if (this._requestTimeoutTimer) {
                    SimpleWebRequestOptions.clearTimeout(this._requestTimeoutTimer);
                    this._requestTimeoutTimer = undefined;
                }

                this._aborted = false;
                this._timedOut = false;
                this._finishHandled = false;

                // Clear the XHR since we technically just haven't started again yet...
                if (this._xhr) {
                    this._xhr.onabort = null;
                    this._xhr.onerror = null;
                    this._xhr.onload = null;
                    this._xhr.onprogress = null;
                    this._xhr.onreadystatechange = null;
                    this._xhr.ontimeout = null;
                    this._xhr = undefined;

                    this._xhrRequestHeaders = undefined;
                }

                if (handleResponse === ErrorHandlingType.PauseUntilResumed) {
                    this._paused = true;
                } else if (handleResponse === ErrorHandlingType.RetryUncountedImmediately) {
                    this._enqueue();
                } else {
                    this._retryTimer = SimpleWebRequestOptions.setTimeout(() => {
                        this._retryTimer = undefined;
                        this._enqueue();
                    }, this._retryExponentialTime.getTimeAndCalculateNext());
                }
            } else {
                // No more retries -- fail.
                this._reject!!!(errResp);
            }
        }

        // Freed up a spot, so let's see if there's other stuff pending
        SimpleWebRequestBase.checkQueueProcessing();
    }