const xhr = new XMLHttpRequest()
xhr.onload = function () {
const headers = new Headers(init?.headers || {})
statusText: xhr.statusText,
url: 'responseURL' in xhr ? xhr.responseURL : headers.get('X-Request-URL')
const body = xhr.response || xhr.responseText
// 如果状态正常, 并且是以 file:// 开头的则表示它可能是在请求本地文件, 正常返回即可
if (request.url.indexOf('file://') === 0 && (xhr.status < 200 || xhr.status > 599)) {
// 请求是属于宏任务, 因此这里包装一层成宏任务, 下面同理
resolve(new Response(body, options))
xhr.onerror = function () {
reject(new TypeError('Network request failed'))
xhr.ontimeout = function () {
reject(new TypeError('Network request timed out'))
xhr.onabort = function () {
reject(new DOMException('Aborted', 'AbortError'))
function fixUrl(url: string): string {
return url === '' && window.location.href ? window.location.href : url
xhr.open(request.method, fixUrl(request.url), true)
if (request.credentials === 'include') {
xhr.withCredentials = true
} else if (request.credentials === 'omit') {
xhr.withCredentials = false
if ('responseType' in xhr) {
if ('FileReader' in window && 'Blob' in window) {
xhr.responseType = 'blob'
} else if('ArrayBuffer' in window) {
xhr.responseType = 'arraybuffer'
if (init && ({}).toString.call(init.headers) === '[object Object]') {
const names: string[] = [];
Object.getOwnPropertyNames(init.headers).forEach(function(name) {
xhr.setRequestHeader(name, init.headers![name])
request.headers.forEach(function(value, name) {
if (names.indexOf(name) === -1) {
xhr.setRequestHeader(name, value)
} else if (Array.isArray(init?.headers)) {
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value)
// 如果设置了 signal 则监听 abort 事件来终止请求
request.signal.addEventListener('abort', abortXhr)
xhr.onreadystatechange = function() {
// DONE (success or failure)
if (xhr.readyState === 4) {
request.signal.removeEventListener('abort', abortXhr)
xhr.send(init!.body as XMLHttpRequestBodyInit)