xfetch-js
Version:
> A extremely simple fetch extension for modern browsers inspired by [sindresorhus/ky](https://github.com/sindresorhus/ky). > > Which aims to be as small as possible and easy to use.
163 lines (158 loc) • 4.69 kB
JavaScript
/*
* XFetch.js
* A extremely simple fetch extension inspired by sindresorhus/ky.
*/
;((root, fn) => {
if (typeof define === 'function' && define.amd) {
define([], fn)
} else if (typeof exports === 'object') {
module.exports = fn()
} else {
root.xf = fn()
}
})(this, () => {
const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head']
class HTTPError extends Error {
constructor(res) {
super(res.statusText)
this.name = 'HTTPError'
this.response = res
}
}
class XResponsePromise extends Promise {}
for (const alias of ['arrayBuffer', 'blob', 'formData', 'json', 'text']) {
// alias for .json() .text() etc...
XResponsePromise.prototype[alias] = function (fn) {
return this.then(res => res[alias]()).then(fn || (x => x))
}
}
const { assign } = Object
function mergeDeep(target, source) {
const isObject = obj => obj && typeof obj === 'object'
if (!isObject(target) || !isObject(source)) {
return source
}
Object.keys(source).forEach(key => {
const targetValue = target[key]
const sourceValue = source[key]
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
target[key] = targetValue.concat(sourceValue)
} else if (isObject(targetValue) && isObject(sourceValue)) {
target[key] = mergeDeep(
Object.assign({}, targetValue),
sourceValue
)
} else {
target[key] = sourceValue
}
})
return target
}
const fromEntries = ent =>
ent.reduce((acc, [k, v]) => ((acc[k] = v), acc), {})
const typeis = (...types) => val =>
types.some(type =>
typeof type === 'string' ? typeof val === type : val instanceof type
)
const isstr = typeis('string')
const isobj = typeis('object')
const isundef = typeis('undefined')
const isstrorobj = v => isstr(v) || isobj(v)
const responseErrorThrower = res => {
if (!res.ok) throw new HTTPError(res)
return res
}
const contentType = 'content-type'
const keysToLowerCase = obj => {
for (const k of Object.keys(obj)) {
const klc = k.toLowerCase()
if (k != klc) {
obj[klc] = obj[k]
delete obj[k]
}
}
}
const extend = (defaultInit = {}) => {
const xfetch = (input, init = {}) => {
mergeDeep(init, defaultInit)
const createQueryString = o =>
new init.URLSearchParams(o).toString()
const parseQueryString = s =>
fromEntries([...new init.URLSearchParams(s).entries()])
const url = new init.URL(input, init.baseURI || undefined)
if (!init.headers) {
init.headers = {}
} else if (typeis(init.Headers)(init.headers)) {
// Transform into object if it is `Headers`
init.headers = fromEntries([...init.headers.entries()])
}
keysToLowerCase(init.headers) // headers is case insensitive
// Add json or form on body
if (init.json) {
init.body = JSON.stringify(init.json)
if (isundef(init.headers[contentType])) {
init.headers[contentType] = 'application/json'
}
} else if (isstrorobj(init.urlencoded)) {
init.body = isstr(init.urlencoded)
? init.urlencoded
: createQueryString(init.urlencoded)
if (isundef(init.headers[contentType])) {
init.headers[contentType] =
'application/x-www-form-urlencoded'
}
} else if (typeis(init.FormData, 'object')(init.formData)) {
// init.formData is data passed by user, init.FormData is FormData constructor
if (!typeis(init.FormData)(init.formData)) {
const fd = new init.FormData()
for (const [k, v] of Object.entries(init.formData)) {
fd.append(k, v)
}
init.formData = fd
}
init.body = init.formData
}
// Querystring
if (init.qs) {
if (isstr(init.qs)) init.qs = parseQueryString(init.qs)
url.search = createQueryString(
assign(
fromEntries([...url.searchParams.entries()]),
init.qs
)
)
}
// same-origin by default
if (!init.credentials) {
init.credentials = 'same-origin'
}
return XResponsePromise.resolve(
init.fetch(url, init).then(responseErrorThrower)
)
}
for (const method of METHODS) {
xfetch[method] = (input, init = {}) => {
init.method = method.toUpperCase()
return xfetch(input, init)
}
}
// Extra methods and classes
xfetch.extend = newDefaultInit =>
extend(assign({}, defaultInit, newDefaultInit))
xfetch.HTTPError = HTTPError
return xfetch
}
const isWindow = typeof document !== 'undefined'
const isBrowser = typeof self !== 'undefined' // works in both window & worker scope
return isBrowser
? extend({
fetch: fetch.bind(self),
URL,
Response,
URLSearchParams,
Headers,
FormData,
baseURI: isWindow ? document.baseURI : '' // since there is no document in webworkers
})
: extend()
})