-
-
Notifications
You must be signed in to change notification settings - Fork 923
/
request.js
199 lines (176 loc) · 6.64 KB
/
request.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
"use strict"
var buildPathname = require("../pathname/build")
var hasOwn = require("../util/hasOwn")
module.exports = function($window, oncompletion) {
function PromiseProxy(executor) {
return new Promise(executor)
}
function makeRequest(url, args) {
return new Promise(function(resolve, reject) {
url = buildPathname(url, args.params)
var method = args.method != null ? args.method.toUpperCase() : "GET"
var body = args.body
var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(body instanceof $window.FormData || body instanceof $window.URLSearchParams)
var responseType = args.responseType || (typeof args.extract === "function" ? "" : "json")
var xhr = new $window.XMLHttpRequest(), aborted = false, isTimeout = false
var original = xhr, replacedAbort
var abort = xhr.abort
xhr.abort = function() {
aborted = true
abort.call(this)
}
xhr.open(method, url, args.async !== false, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
if (assumeJSON && body != null && !hasHeader(args, "content-type")) {
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
}
if (typeof args.deserialize !== "function" && !hasHeader(args, "accept")) {
xhr.setRequestHeader("Accept", "application/json, text/*")
}
if (args.withCredentials) xhr.withCredentials = args.withCredentials
if (args.timeout) xhr.timeout = args.timeout
xhr.responseType = responseType
for (var key in args.headers) {
if (hasOwn.call(args.headers, key)) {
xhr.setRequestHeader(key, args.headers[key])
}
}
xhr.onreadystatechange = function(ev) {
// Don't throw errors on xhr.abort().
if (aborted) return
if (ev.target.readyState === 4) {
try {
var success = (ev.target.status >= 200 && ev.target.status < 300) || ev.target.status === 304 || (/^file:\/\//i).test(url)
// When the response type isn't "" or "text",
// `xhr.responseText` is the wrong thing to use.
// Browsers do the right thing and throw here, and we
// should honor that and do the right thing by
// preferring `xhr.response` where possible/practical.
var response = ev.target.response, message
if (responseType === "json") {
// For IE and Edge, which don't implement
// `responseType: "json"`.
if (!ev.target.responseType && typeof args.extract !== "function") {
// Handle no-content which will not parse.
try { response = JSON.parse(ev.target.responseText) }
catch (e) { response = null }
}
} else if (!responseType || responseType === "text") {
// Only use this default if it's text. If a parsed
// document is needed on old IE and friends (all
// unsupported), the user should use a custom
// `config` instead. They're already using this at
// their own risk.
if (response == null) response = ev.target.responseText
}
if (typeof args.extract === "function") {
response = args.extract(ev.target, args)
success = true
} else if (typeof args.deserialize === "function") {
response = args.deserialize(response)
}
if (success) {
if (typeof args.type === "function") {
if (Array.isArray(response)) {
for (var i = 0; i < response.length; i++) {
response[i] = new args.type(response[i])
}
}
else response = new args.type(response)
}
resolve(response)
}
else {
var completeErrorResponse = function() {
try { message = ev.target.responseText }
catch (e) { message = response }
var error = new Error(message)
error.code = ev.target.status
error.response = response
reject(error)
}
if (xhr.status === 0) {
// Use setTimeout to push this code block onto the event queue
// This allows `xhr.ontimeout` to run in the case that there is a timeout
// Without this setTimeout, `xhr.ontimeout` doesn't have a chance to reject
// as `xhr.onreadystatechange` will run before it
setTimeout(function() {
if (isTimeout) return
completeErrorResponse()
})
} else completeErrorResponse()
}
}
catch (e) {
reject(e)
}
}
}
xhr.ontimeout = function (ev) {
isTimeout = true
var error = new Error("Request timed out")
error.code = ev.target.status
reject(error)
}
if (typeof args.config === "function") {
xhr = args.config(xhr, args, url) || xhr
// Propagate the `abort` to any replacement XHR as well.
if (xhr !== original) {
replacedAbort = xhr.abort
xhr.abort = function() {
aborted = true
replacedAbort.call(this)
}
}
}
if (body == null) xhr.send()
else if (typeof args.serialize === "function") xhr.send(args.serialize(body))
else if (body instanceof $window.FormData || body instanceof $window.URLSearchParams) xhr.send(body)
else xhr.send(JSON.stringify(body))
})
}
// In case the global Promise is some userland library's where they rely on
// `foo instanceof this.constructor`, `this.constructor.resolve(value)`, or
// similar. Let's *not* break them.
PromiseProxy.prototype = Promise.prototype
PromiseProxy.__proto__ = Promise // eslint-disable-line no-proto
function hasHeader(args, name) {
for (var key in args.headers) {
if (hasOwn.call(args.headers, key) && key.toLowerCase() === name) return true
}
return false
}
return {
request: function(url, args) {
if (typeof url !== "string") { args = url; url = url.url }
else if (args == null) args = {}
var promise = makeRequest(url, args)
if (args.background === true) return promise
var count = 0
function complete() {
if (--count === 0 && typeof oncompletion === "function") oncompletion()
}
return wrap(promise)
function wrap(promise) {
var then = promise.then
// Set the constructor, so engines know to not await or resolve
// this as a native promise. At the time of writing, this is
// only necessary for V8, but their behavior is the correct
// behavior per spec. See this spec issue for more details:
// https://github.com/tc39/ecma262/issues/1577. Also, see the
// corresponding comment in `request/tests/test-request.js` for
// a bit more background on the issue at hand.
promise.constructor = PromiseProxy
promise.then = function() {
count++
var next = then.apply(promise, arguments)
next.then(complete, function(e) {
complete()
if (count === 0) throw e
})
return wrap(next)
}
return promise
}
}
}
}