Libon

fetch API 是如何实现的

3Mins #JavaScript
学习浏览器内置 fetch API 是如何实现的

ToC

前置

在学习之前 fetch 之前需要提前了解一下以下 API

core

在来到这个章节以前,默认你已经了解过上面的 API 了。那么首先我们知道 fetch 函数接收两个参数并返回一个 Promise,我们先定义一下:

1
function 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.responseText
24
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 : url
64
} catch (e) {
65
return url
66
}
67
}
68
69
// 打开一个请求
70
xhr.open(request.method, fixUrl(request.url), true)
71
72
// 它的作用是决定是否允许跨域
73
if (request.credentials === 'include') {
74
xhr.withCredentials = true
75
} else if (request.credentials === 'omit') {
76
xhr.withCredentials = false
77
}
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 方法了,不过这里面还缺失了一部分功能,比如:redirectcachekeepalive 功能,但这并不影响我们对其原理实现的学习,在多数情况下,我们只需要了解其核心功能到底做了什么即可,如果要细究,那需要参照 W3C 的规范文档及搭配引擎的内部实现来学习才能达到最好的效果。

以上。


CD ..