fetch/src/next.js

234 lines
5.8 KiB
JavaScript

/**
* 新一代版本
* @author yutent<yutent.io@gmail.com>
* @date 2020/07/31 18:59:47
*/
import { Format, toS } from './lib/format.js'
const nativeFetch = window.fetch
const NOBODY_METHODS = ['GET', 'HEAD']
const FORM_TYPES = {
form: 'application/x-www-form-urlencoded; charset=UTF-8',
json: 'application/json; charset=UTF-8',
text: 'text/plain; charset=UTF-8'
}
const ERRORS = {
10001: 'Argument url is required',
10012: 'Parse error',
10100: 'Request canceled',
10104: 'Request pending...',
10200: 'Ok',
10204: 'No content',
10304: 'Not modified',
10500: 'Internal Server Error',
10504: 'Connected timeout'
}
class _Request {
constructor(url = '', options = {}, owner) {
if (!url) {
throw new Error(ERRORS[10001])
}
// url规范化
url = url.replace(/#.*$/, '')
if (owner.BASE_URL) {
if (!/^([a-z]+:|\/\/)/.test(url)) {
url = owner.BASE_URL + url
}
}
options.method = (options.method || 'get').toUpperCase()
this._owner = owner
this.options = {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'content-type': FORM_TYPES.form
},
body: null,
cache: 'default',
signal: null, // 超时信号, 配置该项时, timeout不再生效
timeout: 30000 // 超时时间, 单位毫秒, 默认30秒
}
if (!options.signal) {
this.control = new AbortController()
options.signal = this.control.signal
}
if (options.headers) {
let headers = this.options.headers
Object.assign(headers, options.headers)
options.headers = headers
}
Object.assign(this.options, options, { url })
if (owner._inject_req) {
owner._inject_req(this.options)
}
return this.__next__()
}
__next__() {
var options = this.options
var params = null
var hasAttach = false // 是否有附件
var crossDomain = false // 是否跨域
var noBody = NOBODY_METHODS.includes(options.method)
/* -------------------------- 1»» 请求的内容 --------------------- */
if (options.body) {
var type = typeof options.body
switch (type) {
case 'number':
case 'string':
this.__type__('text')
params = options.body
break
case 'object':
// 解析表单DOM
if (options.body.nodeName === 'FORM') {
options.method = options.body.method.toUpperCase() || 'POST'
params = Format.parseForm(options.body)
hasAttach = params.constructor === FormData
// 如果是一个 FormData对象,且为不允许携带body的方法,则直接改为POST
} else if (options.body.constructor === FormData) {
hasAttach = true
if (noBody) {
options.method = 'POST'
}
params = options.body
} else {
for (let k in options.body) {
if (toS.call(options.body[k]) === '[object File]') {
hasAttach = true
break
}
}
// 有附件,则改为FormData
if (hasAttach) {
if (noBody) {
options.method = 'POST'
}
params = Format.mkFormData(options.body)
} else {
params = options.body
}
}
}
}
if (hasAttach) {
delete options.headers['content-type']
}
/* -------------------------- 2»» 处理跨域 --------------------- */
try {
let $a = document.createElement('a')
$a.href = options.url
crossDomain = location.protocol !== $a.protocol || location.host !== $a.host
$a = null
} catch (err) {}
if (crossDomain && options.credentials === 'omit') {
delete options.headers['X-Requested-With']
}
/* ------------- 3»» 根据method类型, 处理表单数据 ---------------- */
// 拼接到url上
if (noBody) {
params = Format.param(params)
if (params) {
options.url += (~options.url.indexOf('?') ? '&' : '?') + params
}
} else {
if (!hasAttach) {
if (~options.headers['content-type'].indexOf('json')) {
params = JSON.stringify(params)
} else {
params = Format.param(params)
}
}
}
/* ----------------- 4»» 超时处理 -----------------------*/
if (options.timeout && options.timeout > 0) {
this.timer = setTimeout(_ => {
this.abort()
}, options.timeout)
delete options.timeout
}
/* ----------------- 5»» 构造请求 ------------------- */
var url = options.url
delete options.url
for (let k in options) {
if (options[k] === null || options[k] === undefined || options[k] === '') {
delete options[k]
}
}
return nativeFetch(url, options)
.then(r => {
clearTimeout(this.timer)
let isSucc = r.status >= 200 && r.status < 400
if (this._owner._inject_res) {
r = this._owner._inject_res(r)
}
if (isSucc) {
return r
} else {
return Promise.reject(r)
}
})
.catch(e => {
clearTimeout(this.timer)
return Promise.reject(e)
})
}
abort() {
this.control.abort()
}
__type__(type) {
this.options.headers['content-type'] = FORM_TYPES[type]
}
}
function inject(target) {
target.inject = {
request(callback) {
target._inject_req = callback
},
response(callback) {
target._inject_res = callback
}
}
}
const _fetch = function (url, options) {
return new _Request(url, options, _fetch)
}
_fetch.create = function () {
var another = function (url, options) {
return new _Request(url, options, another)
}
inject(another)
return another
}
inject(_fetch)
export default _fetch
全新的ajax封装。分2个版本, 一个基于XMLHttpRequest, 一个基于window.fetch
JavaScript 100%