mithril-vndb/request/request.js
Isiah Meadows 7cbc15e7a2
Fix m.request/m.jsonp to not mutate arguments, simplify code (#2288)
I basically recast it to remove 99% of the duplication. They're
basically the same function mod how they fire their requests and append
query parameters.
2018-11-28 20:10:46 -05:00

168 lines
5.1 KiB
JavaScript

"use strict"
var buildQueryString = require("../querystring/build")
module.exports = function($window, Promise) {
var callbackCount = 0
var oncompletion
function makeRequest(factory) {
return function(url, args) {
if (typeof url !== "string") { args = url; url = url.url }
else if (args == null) args = {}
var promise = new Promise(function(resolve, reject) {
factory(url, args, function (data) {
if (typeof args.type === "function") {
if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
data[i] = new args.type(data[i])
}
}
else data = new args.type(data)
}
resolve(data)
}, reject)
})
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
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
}
}
}
function hasHeader(args, name) {
for (var key in args.headers) {
if ({}.hasOwnProperty.call(args.headers, key) && name.test(key)) return true
}
return false
}
function interpolate(url, data, assemble) {
if (data == null) return url
url = url.replace(/:([^\/]+)/gi, function (m, key) {
return data[key] != null ? data[key] : m
})
if (assemble && data != null) {
var querystring = buildQueryString(data)
if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring
}
return url
}
return {
request: makeRequest(function(url, args, resolve, reject) {
var method = args.method != null ? args.method.toUpperCase() : "GET"
var useBody = method !== "GET" && method !== "TRACE" &&
(typeof args.useBody !== "boolean" || args.useBody)
var data = args.data
var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData)
if (useBody) {
if (typeof args.serialize === "function") data = args.serialize(data)
else if (!(data instanceof $window.FormData)) data = JSON.stringify(data)
}
var xhr = new $window.XMLHttpRequest(),
aborted = false,
_abort = xhr.abort
xhr.abort = function abort() {
aborted = true
_abort.call(xhr)
}
xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
if (assumeJSON && useBody && !hasHeader(args, /^content-type$/i)) {
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
}
if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) {
xhr.setRequestHeader("Accept", "application/json, text/*")
}
if (args.withCredentials) xhr.withCredentials = args.withCredentials
if (args.timeout) xhr.timeout = args.timeout
if (args.responseType) xhr.responseType = args.responseType
for (var key in args.headers) {
if ({}.hasOwnProperty.call(args.headers, key)) {
xhr.setRequestHeader(key, args.headers[key])
}
}
if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
xhr.onreadystatechange = function() {
// Don't throw errors on xhr.abort().
if(aborted) return
if (xhr.readyState === 4) {
try {
var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url)
var response = xhr.responseText
if (typeof args.extract === "function") {
response = args.extract(xhr, args)
success = true
} else if (typeof args.deserialize === "function") {
response = args.deserialize(response)
} else {
try {response = response ? JSON.parse(response) : null}
catch (e) {throw new Error("Invalid JSON: " + response)}
}
if (success) resolve(response)
else {
var error = new Error(xhr.responseText)
error.code = xhr.status
error.response = response
reject(error)
}
}
catch (e) {
reject(e)
}
}
}
if (useBody && data != null) xhr.send(data)
else xhr.send()
}),
jsonp: makeRequest(function(url, args, resolve, reject) {
var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
var script = $window.document.createElement("script")
$window[callbackName] = function(data) {
script.parentNode.removeChild(script)
resolve(data)
delete $window[callbackName]
}
script.onerror = function() {
script.parentNode.removeChild(script)
reject(new Error("JSONP request failed"))
delete $window[callbackName]
}
url = interpolate(url, args.data, true)
script.src = url + (url.indexOf("?") < 0 ? "?" : "&") +
encodeURIComponent(args.callbackKey || "callback") + "=" +
encodeURIComponent(callbackName)
$window.document.documentElement.appendChild(script)
}),
setCompletionCallback: function(callback) {
oncompletion = callback
},
}
}