ToC
前置
在学习之前 fetch 之前需要提前了解一下以下 API
- Promise
- Headers
- Request
- Response
- DOMException:在调用方法或访问 Web API 属性时发生的不正常事件时抛出的错误类型
core
在来到这个章节以前,默认你已经了解过上面的 API 了。那么首先我们知道 fetch 函数接收两个参数并返回一个 Promise,我们先定义一下:
1function fetch(input: RequestInfo | URL, init?: RequestInit) {2 return new Promise(function(resolve, reject) {3 const request = new Request(input, init)4
5 // 如果这个请求已经被终止了则不继续6 if (request.signal && request.signal.aborted) {7 return reject(new DOMException('Aborted', 'AbortError'))8 }9
10 // 创建一个 xhr 请求对象114 collapsed lines
11 const xhr = new XMLHttpRequest()12
13 // 当请求完成时14 xhr.onload = function () {15 const headers = new Headers(init?.headers || {})16 const options = {17 headers,18 status: xhr.status,19 statusText: xhr.statusText,20 url: 'responseURL' in xhr ? xhr.responseURL : headers.get('X-Request-URL')21 }22
23 const body = xhr.response || xhr.responseText24
25 // 如果状态正常, 并且是以 file:// 开头的则表示它可能是在请求本地文件, 正常返回即可26 if (request.url.indexOf('file://') === 0 && (xhr.status < 200 || xhr.status > 599)) {27 options.status = 200;28 }29
30 // 请求是属于宏任务, 因此这里包装一层成宏任务, 下面同理31 setTimeout(() => {32 resolve(new Response(body, options))33 }, 0)34 }35
36 // 当请求出现错误时37 xhr.onerror = function () {38 setTimeout(function() {39 reject(new TypeError('Network request failed'))40 }, 0)41 }42
43 // 当请求出现超时时44 xhr.ontimeout = function () {45 setTimeout(function() {46 reject(new TypeError('Network request timed out'))47 }, 0)48 }49
50 // 当请求被手动或在内部终止时51 xhr.onabort = function () {52 setTimeout(function() {53 reject(new DOMException('Aborted', 'AbortError'))54 }, 0)55 }56
57 function abortXhr() {58 xhr.abort()59 }60
61 function fixUrl(url: string): string {62 try {63 return url === '' && window.location.href ? window.location.href : url64 } catch (e) {65 return url66 }67 }68
69 // 打开一个请求70 xhr.open(request.method, fixUrl(request.url), true)71
72 // 它的作用是决定是否允许跨域73 if (request.credentials === 'include') {74 xhr.withCredentials = true75 } else if (request.credentials === 'omit') {76 xhr.withCredentials = false77 }78
79 // 可能返回的是并不是字符串,而是二进制数据80 if ('responseType' in xhr) {81 // 如果是可读的二进制数据则设置82 if ('FileReader' in window && 'Blob' in window) {83 xhr.responseType = 'blob'84 } else if('ArrayBuffer' in window) {85 xhr.responseType = 'arraybuffer'86 }87 }88
89 // 如果传入了请求头则设置90 if (init && ({}).toString.call(init.headers) === '[object Object]') {91 const names: string[] = [];92 Object.getOwnPropertyNames(init.headers).forEach(function(name) {93 names.push(name)94 xhr.setRequestHeader(name, init.headers![name])95 })96 request.headers.forEach(function(value, name) {97 if (names.indexOf(name) === -1) {98 xhr.setRequestHeader(name, value)99 }100 })101 } else if (Array.isArray(init?.headers)) {102 // 设置数组格式的请求头103 request.headers.forEach(function(value, name) {104 xhr.setRequestHeader(name, value)105 })106 }107
108 // 如果设置了 signal 则监听 abort 事件来终止请求109 if (request.signal) {110 request.signal.addEventListener('abort', abortXhr)111
112 xhr.onreadystatechange = function() {113 // DONE (success or failure)114 if (xhr.readyState === 4) {115 // 如果响应体正常结束则移除事件监听116 request.signal.removeEventListener('abort', abortXhr)117 }118 }119 }120
121 // 发送请求122 xhr.send(init!.body as XMLHttpRequestBodyInit)123 })124}
这样我们就得到了一个可用的自定义 fetch 方法了,不过这里面还缺失了一部分功能,比如:redirect 、cache 和 keepalive 功能,但这并不影响我们对其原理实现的学习,在多数情况下,我们只需要了解其核心功能到底做了什么即可,如果要细究,那需要参照 W3C 的规范文档及搭配引擎的内部实现来学习才能达到最好的效果。
以上。