440 lines
12 KiB
JavaScript
440 lines
12 KiB
JavaScript
/**
|
||
*
|
||
* @authors yutent (yutent@doui.cc)
|
||
* @date 2018-03-25 23:59:13
|
||
* @version $Id$
|
||
*/
|
||
|
||
'use strict'
|
||
import Format from './lib/format'
|
||
|
||
// 本地协议/头 判断正则
|
||
const rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
|
||
const log = console.log
|
||
|
||
const noop = function(e, res) {
|
||
this.defer.resolve(res)
|
||
}
|
||
|
||
let isLocal = false
|
||
try {
|
||
isLocal = rlocalProtocol.test(location.ptyperotocol)
|
||
} catch (e) {}
|
||
|
||
let originAnchor = document.createElement('a')
|
||
originAnchor.href = location.href
|
||
|
||
const NOBODY_METHODS = ['GET', 'HEAD']
|
||
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'
|
||
}
|
||
|
||
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 convert = {
|
||
text(val) {
|
||
return val
|
||
},
|
||
xml(val, xml) {
|
||
return xml !== undefined ? xml : Format.parseXML(val)
|
||
},
|
||
html(val) {
|
||
return Format.parseHTML(val)
|
||
},
|
||
json(val) {
|
||
return JSON.parse(val)
|
||
},
|
||
script(val) {
|
||
return Format.parseJS(val)
|
||
}
|
||
}
|
||
|
||
class _Request {
|
||
constructor(url = '', method = 'GET', param = {}) {
|
||
if (!url) {
|
||
throw new Error(ERRORS[10001])
|
||
}
|
||
|
||
// url规范化
|
||
url = url.replace(/#.*$/, '').replace(/^\/\//, location.protocol + '//')
|
||
|
||
method = method.toUpperCase()
|
||
|
||
this.xhr = new XMLHttpRequest()
|
||
this.defer = Promise.defer()
|
||
this.opt = {
|
||
url,
|
||
method,
|
||
headers: {},
|
||
data: {},
|
||
dataType: 'text',
|
||
withCredentials: false // 跨域选项,是否验证凭证
|
||
}
|
||
|
||
// 取消网络请求
|
||
this.defer.promise.abort = () => {
|
||
this.cancel = true
|
||
this.xhr.abort()
|
||
}
|
||
this.__next__(Object.assign({}, request.__INIT__, param))
|
||
return this.defer.promise
|
||
}
|
||
|
||
__next__(param) {
|
||
/* -------------------------------------------------------------- */
|
||
/* ------------------------ 1»» 配置头信息 ---------------------- */
|
||
/* -------------------------------------------------------------- */
|
||
if (param.headers) {
|
||
Object.assign(this.opt.headers, param.headers)
|
||
}
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* --------- 2»» 设置表单类型, 其中 form-data不能手动设置 ---------- */
|
||
/* -------------------------------------------------------------- */
|
||
let hasAttach = false
|
||
if (param.formType) {
|
||
switch (param.formType) {
|
||
case 'form':
|
||
this.__set__('form')
|
||
break
|
||
case 'json':
|
||
this.__set__('json')
|
||
break
|
||
case 'form-data':
|
||
this.opt.method = 'POST'
|
||
hasAttach = true
|
||
break
|
||
default:
|
||
if (NOBODY_METHODS.includes(this.opt.method)) {
|
||
this.__set__('form')
|
||
} else {
|
||
this.__set__('text')
|
||
}
|
||
}
|
||
} else {
|
||
this.__set__('form')
|
||
}
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* ------------------- 3»» 设置缓存 ---------------------------- */
|
||
/* -------------------------------------------------------------- */
|
||
if (param.cache) {
|
||
if (NOBODY_METHODS.includes(this.opt.method)) {
|
||
this.opt.cache = true
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* ------------------- 4»» 设置超时时间(毫秒) --------------------- */
|
||
/* -------------------------------------------------------------- */
|
||
param.timeout = param.timeout >>> 0
|
||
if (param.timeout > 0) {
|
||
this.opt.timeout = param.timeout
|
||
}
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* -------------------------- 5»» 请求的内容 --------------------- */
|
||
/* -------------------------------------------------------------- */
|
||
if (param.data) {
|
||
let type = typeof param.data
|
||
|
||
switch (type) {
|
||
case 'number':
|
||
case 'string':
|
||
this.__set__('text')
|
||
this.opt.data = param.data
|
||
break
|
||
case 'object':
|
||
// 解析表单DOM
|
||
if (param.data.nodeName === 'FORM') {
|
||
this.opt.method = param.data.method.toUpperCase() || 'POST'
|
||
|
||
this.opt.data = Format.parseForm(param.data)
|
||
hasAttach = this.opt.data.constructor === FormData
|
||
|
||
if (hasAttach) {
|
||
delete this.opt.headers['content-type']
|
||
}
|
||
// 如果是一个 FormData对象
|
||
// 则直接改为POST
|
||
} else if (param.data.constructor === FormData) {
|
||
hasAttach = true
|
||
this.opt.method = 'POST'
|
||
delete this.opt.headers['content-type']
|
||
this.opt.data = param.data
|
||
} else {
|
||
// 有附件,则改为FormData
|
||
if (hasAttach) {
|
||
this.opt.data = Format.mkFormData(param.data)
|
||
} else {
|
||
this.opt.data = param.data
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* -------------------------- 6»» 处理跨域 --------------------- */
|
||
/* -------------------------------------------------------------- */
|
||
if (param.withCredentials) {
|
||
this.opt.withCredentials = true
|
||
}
|
||
try {
|
||
let anchor = document.createElement('a')
|
||
anchor.href = this.opt.url
|
||
|
||
this.opt.crossDomain =
|
||
originAnchor.protocol !== anchor.protocol ||
|
||
originAnchor.host !== anchor.host
|
||
} catch (err) {}
|
||
|
||
// 6.1»» 进一步处理跨域
|
||
// 非跨域或跨域但支持Cors时自动加上一条header信息,用以标识这是ajax请求
|
||
// 如果是跨域,开启Cors会需要服务端额外返回一些headers
|
||
|
||
if (this.opt.crossDomain) {
|
||
if (this.opt.withCredentials) {
|
||
this.xhr.withCredentials = true
|
||
this.opt.headers['X-Requested-With'] = 'XMLHttpRequest'
|
||
}
|
||
} else {
|
||
this.opt.headers['X-Requested-With'] = 'XMLHttpRequest'
|
||
}
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* ------------- 7»» 根据method类型, 处理g表单数据 ---------------- */
|
||
/* -------------------------------------------------------------- */
|
||
// 是否允许发送body
|
||
let allowBody = !NOBODY_METHODS.includes(this.opt.method)
|
||
if (allowBody) {
|
||
if (!hasAttach) {
|
||
if (param.formType === 'json') {
|
||
this.opt.data = JSON.stringify(this.opt.data)
|
||
} else {
|
||
this.opt.data = Format.param(this.opt.data)
|
||
}
|
||
}
|
||
} else {
|
||
// 否则拼接到url上
|
||
this.opt.data = Format.param(this.opt.data)
|
||
|
||
this.opt.url += (/\?/.test(this.opt.url) ? '&' : '?') + this.opt.data
|
||
|
||
if (this.opt.cache === false) {
|
||
this.opt.url +=
|
||
(/\?/.test(this.opt.url) ? '&' : '?') + '_=' + Math.random()
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* ------------- 8»» 设置响应的数据类型 ---------------- */
|
||
/* -------------------------------------------------------------- */
|
||
// arraybuffer | blob | document | json | text
|
||
if (param.dataType) {
|
||
this.opt.dataType = param.dataType.toLowerCase()
|
||
}
|
||
this.xhr.responseType = this.opt.dataType
|
||
|
||
/* -------------------------------------------------------------- */
|
||
/* ------------- 9»» 构造请求 ---------------- */
|
||
/* -------------------------------------------------------------- */
|
||
|
||
// response ready
|
||
this.xhr.onreadystatechange = ev => {
|
||
if (this.opt.timeout > 0) {
|
||
this.opt['time' + this.xhr.readyState] = ev.timeStamp
|
||
if (this.xhr.readyState === 4) {
|
||
this.opt.isTimeout =
|
||
this.opt.time4 - this.opt.time1 > this.opt.timeout
|
||
}
|
||
}
|
||
|
||
if (this.xhr.readyState !== 4) {
|
||
return
|
||
}
|
||
|
||
this.__dispatch__(this.opt.isTimeout)
|
||
}
|
||
|
||
// 9.1»» 初始化xhr
|
||
this.xhr.open(this.opt.method, this.opt.url, true)
|
||
|
||
// 9.2»» 设置头信息
|
||
for (let i in this.opt.headers) {
|
||
this.xhr.setRequestHeader(i, this.opt.headers[i])
|
||
}
|
||
|
||
// 9.3»» 发起网络请求
|
||
this.xhr.send(this.opt.data)
|
||
|
||
// 9.4»» 超时处理
|
||
if (this.opt.timeout && this.opt.timeout > 0) {
|
||
this.xhr.timeout = this.opt.timeout
|
||
}
|
||
}
|
||
|
||
__set__(type) {
|
||
this.opt.headers['content-type'] = FORM_TYPES[type]
|
||
}
|
||
|
||
__dispatch__(isTimeout) {
|
||
let result = {
|
||
status: 200,
|
||
statusText: 'ok',
|
||
text: '',
|
||
body: '',
|
||
error: null
|
||
}
|
||
|
||
// 主动取消
|
||
if (this.cancel) {
|
||
return this.__cancel__(result)
|
||
}
|
||
|
||
// 超时
|
||
if (isTimeout) {
|
||
return this.__timeout__(result)
|
||
}
|
||
|
||
// 是否请求成功(resful规范)
|
||
let isSucc = this.xhr.status >= 200 && this.xhr.status < 400
|
||
|
||
let headers = this.xhr.getAllResponseHeaders().split('\n') || []
|
||
let contentType = ''
|
||
|
||
//处理返回的 Header, 拿到content-type
|
||
for (let it of headers) {
|
||
it = it.trim()
|
||
if (it) {
|
||
it = it.split(':')
|
||
let tmp = it.shift().toLowerCase()
|
||
if (tmp === 'content-type') {
|
||
contentType = it
|
||
.join(':')
|
||
.trim()
|
||
.toLowerCase()
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if (isSucc) {
|
||
result.status = this.xhr.status
|
||
if (result.status === 204) {
|
||
result.statusText = ERRORS[10204]
|
||
} else if (result.status === 304) {
|
||
result.statusText = ERRORS[10304]
|
||
}
|
||
} else {
|
||
result.status = this.xhr.status || 500
|
||
result.statusText = this.xhr.statusText || ERRORS[10500]
|
||
result.error = new Error(result.statusText)
|
||
}
|
||
// log(this.opt.dataType, this.xhr)
|
||
switch (this.opt.dataType) {
|
||
case 'arraybuffer':
|
||
case 'blob':
|
||
case 'document':
|
||
case 'json':
|
||
result.text = result.body = this.xhr.response
|
||
break
|
||
// text
|
||
default:
|
||
try {
|
||
//处理返回的数据
|
||
let dataType = contentType.match(/json|xml|script|html/)
|
||
|
||
dataType = (dataType && dataType[0].toLowerCase()) || 'text'
|
||
|
||
result.text = this.xhr.response
|
||
result.body = convert[dataType](result.text, this.xhr.response)
|
||
} catch (err) {
|
||
result.error = err
|
||
result.statusText = ERRORS[10012]
|
||
}
|
||
break
|
||
}
|
||
this.__success__(isSucc, result)
|
||
}
|
||
|
||
__success__(isSucc, result) {
|
||
if (isSucc) {
|
||
this.defer.resolve(result)
|
||
} else {
|
||
this.defer.reject(result)
|
||
}
|
||
delete this.xhr
|
||
delete this.opt
|
||
delete this.defer
|
||
}
|
||
|
||
__cancel__(result) {
|
||
result.status = 0
|
||
result.statusText = ERRORS[10100]
|
||
result.error = new Error(ERRORS[10100])
|
||
|
||
this.defer.reject(result)
|
||
|
||
delete this.xhr
|
||
delete this.opt
|
||
delete this.defer
|
||
}
|
||
|
||
__timeout__(result) {
|
||
result.status = 504
|
||
result.statusText = ERRORS[10504]
|
||
result.error = new Error(ERRORS[10504])
|
||
|
||
this.defer.reject(result)
|
||
|
||
delete this.xhr
|
||
delete this.opt
|
||
delete this.defer
|
||
}
|
||
}
|
||
|
||
if (!window.request) {
|
||
window.request = {
|
||
get(url, param = {}) {
|
||
return new _Request(url, 'GET', param)
|
||
},
|
||
post(url, param = {}) {
|
||
return new _Request(url, 'POST', param)
|
||
},
|
||
upload(url, param = {}) {
|
||
param.formType = 'form-data'
|
||
return this.post(url, param)
|
||
},
|
||
download(url, param = {}) {
|
||
param.dataType = 'blob'
|
||
return this.get(url, param)
|
||
},
|
||
open(url, method = 'GET', param = {}) {
|
||
if (typeof method === 'object') {
|
||
param = method
|
||
method = 'GET'
|
||
}
|
||
return new _Request(url, method, param)
|
||
},
|
||
version: '2.0.0-normal',
|
||
init(param = {}) {
|
||
this.__INIT__ = param
|
||
}
|
||
}
|
||
Anot.ui.request = request.version
|
||
}
|
||
|
||
export default request
|
JavaScript
95.2%
CSS
4.8%