This repository has been archived on 2023-08-30. You can view files and clone it, but cannot push or open issues/pull-requests.
bytedo
/
wcui
Archived
1
0
Fork 0

update request

old
宇天 2019-02-20 22:06:13 +08:00
parent 755bed9fc0
commit 2fb181bcc2
2 changed files with 208 additions and 504 deletions

View File

@ -10,37 +10,23 @@ import Format from './lib/format'
// 本地协议/头 判断正则
const rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
const rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/gm
const encode = encodeURIComponent
const decode = decodeURIComponent
const toS = Object.prototype.toString
const win = window
const doc = win.document
const log = console.log
const noop = function(e, res) {
this.defer.resolve(res)
}
const Xhr = function() {
return new XMLHttpRequest()
}
const supportCors = 'withCredentials' in Xhr()
let isLocal = false
try {
isLocal = rlocalProtocol.test(location.ptyperotocol)
} catch (e) {}
let originAnchor = doc.createElement('a')
let originAnchor = document.createElement('a')
originAnchor.href = location.href
const noBodyMethods = ['GET', 'HEAD', 'JSONP']
const error = {
const NOBODY_METHODS = ['GET', 'HEAD', 'JSONP']
const ERROR = {
10001: 'argument url is required',
10002: 'method "set" required an object or 2 args',
10003: 'method "send" can not call by different way',
10004: 'method "send" arguments error',
10005: 'method "send" required an object/string or 2 args',
10006: 'method "field" required an object or 2 args',
10011: 'Promise required a callback',
10012: 'Parse error',
10104: 'Request pending...',
@ -48,7 +34,10 @@ const error = {
10204: 'no content',
10304: 'not modified',
10500: 'Internal Server Error',
10504: 'Connected timeout',
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'
@ -78,14 +67,18 @@ const convert = {
}
class _Request {
constructor(url = '', method = 'GET') {
constructor(url = '', method = 'GET', param = {}) {
if (!url) {
throw new Error(error[10001])
}
// url规范化
url = url.replace(/#.*$/, '').replace(/^\/\//, location.protocol + '//')
method = method.toUpperCase()
this.transport = Object.create(null)
this.xhr = Xhr()
this.xhr = new XMLHttpRequest()
this.defer = Promise.defer()
this.opt = {
url,
@ -98,268 +91,144 @@ class _Request {
.toString(16)
.slice(2)
}
return this.__open__(param)
}
_formData() {
if (this.opt.form) {
let data = Format.parseForm(this.opt.form)
Format.merge(this.opt.data, data)
__open__(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') {
hasAttach = true
this.opt.method = 'POST'
delete this.opt.headers['content-type']
this.opt.data = this.__parseForm__(param.data)
} else if (param.data.constructor === FormData) {
hasAttach = true
// 如果是一个 FormData对象
// 则直接改为POST
this.opt.method = 'POST'
delete this.opt.headers['content-type']
this.opt.data = param.data
} else {
// 有附件,则改为FormData
if (hasAttach) {
let form = new FormData()
for (let i in this.opt.data) {
let el = this.opt.data[i]
for (let i in param.data) {
let el = param.data[i]
if (Array.isArray(el)) {
el.forEach(function(it) {
form.append(i + '[]', it)
})
} else {
form.append(i, this.opt.data[i])
form.append(i, param.data[i])
}
}
return form
}
_jsonp(cb) {
win[cb] = function(val) {
delete win[cb]
request.cache[cb] = val
}
}
_dispatch(isTimeout) {
if (!this.transport) {
return this.defer.reject(error[10104])
}
let result = {
response: {
url: this.opt.url,
headers: { 'content-type': '' }
},
request: {
url: this.opt.url,
headers: this.opt.headers
},
status: isTimeout === null ? 504 : 200,
statusText: isTimeout === null ? 'Connected timeout' : 'ok',
text: '',
body: '',
error: null
}
//状态为4,既已成功, 则清除超时
clearTimeout(this.opt.timeoutID)
if (this.transport.nodeType && this.opt.method === 'JSONP') {
//移除script
this.transport.parentNode.removeChild(this.transport)
//超时返回
if (!isTimeout) {
let exec =
!this.transport.readyState ||
this.transport.readyState === 'loaded' ||
this.transport.readyState === 'complete'
if (exec) {
result.body = convert.jsonp(this.opt.data.callback)
result.text = JSON.stringify(result.body)
}
}
this.defer.resolve(result)
this.opt.data = form
} else {
//成功的回调
let isSucc = isTimeout
? false
: this.transport.status >= 200 && this.transport.status < 400
let headers =
(!isTimeout && this.transport.getAllResponseHeaders().split('\n')) || []
//处理返回的Header
headers.forEach(function(it, i) {
it = it.trim()
if (it) {
it = it.split(':')
result.response.headers[it.shift().toLowerCase()] = it
.join(':')
.trim()
this.opt.data = param.data
}
}
})
if (isSucc) {
result.status = this.transport.status
if (result.status === 204) {
result.statusText = error[10204]
} else if (result.status === 304) {
result.statusText = error[10304]
}
} else {
result.status = isTimeout ? 504 : this.transport.status || 500
result.statusText = isTimeout
? error[10504]
: this.transport.statusText || error[10500]
result.error = new Error(result.statusText)
}
// 6»» 处理跨域
try {
//处理返回的数据
var dataType = result.response.headers['content-type'].match(
/json|xml|script|html/i
) || ['text']
let ancher = document.createElement('a')
ancher.href = this.opt.url
dataType = dataType[0].toLowerCase()
result.text = isTimeout
? ''
: this.transport.responseText || this.transport.responseXML
result.body = convert[dataType](
result.text,
!isTimeout && this.transport.responseXML
)
this.opt.crossDomain =
originAnchor.protocol !== anchor.protocol ||
originAnchor.host !== anchor.host
} catch (err) {
result.error = err
result.statusText = error[10012]
this.opt.crossDomain = true
}
if (result.status >= 200 && result.status < 400) {
this.defer.resolve(result)
// 6.1»» 进一步处理跨域
if (this.opt.method === 'JSONP') {
// 如果非跨域,则转回 xhr GET
if (this.opt.crossDomain) {
this.opt.data.callback =
this.opt.data.callback || `jsonp${request.cid++}`
} else {
this.defer.reject(result)
this.opt.method = 'GET'
}
}
delete this.transport
delete this.opt
delete this.xhr
}
// 设置表单类型, 支持3种, form(即x-www-form-urlencoded)/json/text
type(type) {
// 如果已经是带附件上传的表单,不再支持修改表单类型
if (this.opt.formType === 'form-data') {
return this
// 6.2»»
// 如果不是JSONP则自动加上一条header信息用以标识这是ajax请求
// 如果是跨域,在支持Cors时, 自动加上支持(这一步会需要服务端额外返回一些headers)
if (this.opt.method !== 'JSONP') {
this.opt.headers['X-Requested-With'] = 'XMLHttpRequest'
}
if (this.opt.crossDomain) {
supportCors && (this.xhr.withCredentials = true)
}
this.opt.formType = type || 'form'
// 7»» 根据method类型, 处理g表单数据
// 不是POST方式, 强制为x-www-form-urlencoded
if (type === 'form' || noBodyMethods.indexOf(this.opt.method) > -1) {
this.set('content-type', error.form)
} else if (type === 'json') {
this.set('content-type', error.json)
} else {
this.set('content-type', error.text)
// 是否允许发送body
let allowBody = !NOBODY_METHODS.includes(this.opt.method)
if (allowBody) {
if (!hasAttach && typeof this.opt.data === 'object') {
this.opt.data = JSON.stringify(this.opt.data)
}
return this
}
//设置头信息
set(key, val) {
// 已经发起请求之后,不再允许追加头信息了
if (!this.transport) {
return this
}
let obj = {}
if (arguments.length === 1) {
if (typeof key !== 'object') {
this.defer.reject(error[10002])
return this
}
obj = key
} else if (arguments.length === 2) {
if (typeof key === 'string' && val !== undefined) {
obj[key] = val
}
} else {
this.defer.reject(error[10002])
return this
}
for (let k in obj) {
// 全转小写,避免重复写入
let v = obj[k]
k = k.toLowerCase()
this.opt.headers[k] = v
}
return this
}
// 设置请求数据(POST方式会放入body, GET则拼接到url上)
send(key, val) {
if (!this.transport) {
return this
}
if (arguments.length === 1) {
if (typeof key === 'string') {
this.opt.data = key
} else if (typeof key === 'object') {
if (typeof this.opt.data !== 'object') {
this.defer.reject(error[10003])
return this
}
Format.merge(this.opt.data, key)
} else {
this.defer.reject(error[10004])
}
} else if (arguments.length === 2) {
if (typeof key !== 'string') {
this.defer.reject(error[10004])
return this
}
if (val === undefined) {
delete this.opt.data[key]
} else {
this.opt.data[key] = val
}
} else {
this.defer.reject(error[10005])
}
return this
}
//该方法用于 form-data类型的post请求的参数设置
field(key, val) {
if (!this.transport) {
return this
}
// 此类型优先级最高
this.opt.formType = 'form-data'
this.opt.method = 'POST'
if (!this.opt.data || typeof this.opt.data !== 'object') {
this.opt.data = {}
}
if (arguments.length === 1 && typeof key === 'object') {
Format.merge(this.opt.data, key)
} else if (arguments.length === 2) {
this.opt.data[key] = val
} else {
this.defer.reject(error[10006])
}
return this
}
//设置缓存
cache(bool) {
if (!this.transport) {
return this
}
if (noBodyMethods.indexOf(this.opt.method) > -1) {
this.opt.cache = !!bool
}
return this
}
// 取消网络请求
abort() {
this.opt.abort = () => {
delete this.transport
if (!this.opt.form) {
this.xhr.abort()
@ -368,202 +237,43 @@ class _Request {
return this
}
//超时设置, 单位毫秒
timeout(time) {
if (typeof time !== 'number' || time < 1) {
return this
}
this.opt.timeout = time
return this
}
form(form) {
if (typeof form === 'object' && form.nodeName === 'FORM') {
this.opt.method = 'POST'
this.opt.form = form
}
return this
}
then(cb) {
if (typeof cb !== 'function') {
this.defer.reject(error[10011])
this.defer.resolve(this.opt)
return this.defer.promise
}
// 回调已执行, 或已取消, 则直接返回, 防止重复执行
if (!this.transport) {
return this.defer.promise
__set__(type) {
this.opt.headers['content-type'] = FORM_TYPES[type]
}
// ------------------------------------------
// 1. url规范化
// ------------------------------------------
this.opt.url = this.opt.url
.replace(/#.*$/, '')
.replace(/^\/\//, location.protocol + '//')
// ------------------------------------------
// 2. 处理跨域
// ------------------------------------------
// 2.1 判断是否跨域
if (typeof this.opt.crossDomain !== 'boolean') {
var anchor = doc.createElement('a')
try {
anchor.href = this.opt.url
// IE7及以下浏览器 '1'[0]的结果是 undefined
// IE7下需要获取绝对路径
var absUrl = !'1'[0] ? anchor.getAttribute('href', 4) : anchor.href
anchor.href = absUrl
anchor.async = true
this.opt.crossDomain =
originAnchor.protocol !== anchor.protocol ||
originAnchor.host !== anchor.host
} catch (e) {
this.opt.crossDomain = true
_jsonp(cb) {
window[cb] = function(val) {
delete window[cb]
request.cache[cb] = val
}
}
}
// 2.2 进一步处理跨域配置
if (this.opt.method === 'JSONP') {
//如果没有跨域自动转回xhr GET
if (!this.opt.crossDomain) {
this.opt.method = 'GET'
} else {
this.opt.data['callback'] =
this.opt.data['callback'] || 'jsonp' + request.cid++
this._jsonp(this.opt.data['callback']) //创建临时处理方法
}
}
// 2.3 如果不是JSONP则自动加上一条header信息用以标识这是ajax请求
if (this.opt.method !== 'JSONP') {
this.set('X-Requested-With', 'XMLHttpRequest')
}
if (this.opt.crossDomain) {
supportCors && (this.xhr.withCredentials = true)
}
// ------------------------------------------
// 3. 解析 data
// ------------------------------------------
this.opt.param = Format.param(this.opt.data)
// ------------------------------------------
// 4. 修正默认表单类型
// ------------------------------------------
if (!this.opt.formType) {
this.type('form')
}
// ------------------------------------------
// 5. 根据method类型,处理body
// ------------------------------------------
let hasBody = noBodyMethods.indexOf(this.opt.method) < 0 //是否为post请求
if (!hasBody) {
//GET请求直接把参数拼接到url上
if (this.opt.param) {
this.opt.url += (/\?/.test(this.opt.url) ? '&' : '?') + this.opt.param
}
//加随机值,避免缓存
if (this.opt.cache === false) {
this.opt.url +=
(/\?/.test(this.opt.url) ? '&' : '?') + '_=' + Math.random()
}
} else {
if (this.opt.formType === 'form-data') {
delete this.opt.headers['content-type']
this.opt.param = this._formData()
} else if (this.opt.formType !== 'form') {
if (typeof this.opt.data === 'object') {
this.opt.data = JSON.stringify(this.opt.data)
}
this.opt.param = this.opt.data
}
}
// ------------------------------------------
// 6. 构造并发起请求
// ------------------------------------------
// 6.1 jsonp
if (this.opt.method === 'JSONP') {
// 6.1.1 构造script并插入
this.transport = doc.createElement('script')
this.transport.onerror = this.transport.onload = () => {
this._dispatch()
}
this.transport.src = this.opt.url
doc.head.insertBefore(this.transport, doc.head.firstChild)
// 6.1.2 超时处理
if (this.opt.timeout && this.opt.timeout > 0) {
this.opt.timeoutID = setTimeout(() => {
this.transport.onerror = this.transport.onload = null
this._dispatch(true)
}, this.opt.timeout)
}
} else {
this.transport = this.xhr
// 6.2 非jsonp
// 6.2.1 监听http状态
this.xhr.onreadystatechange = ev => {
if (this.opt.timeout && 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)
}
// 6.2.2 初始化xhr提交
this.xhr.open(this.opt.method, this.opt.url, true)
// 6.2.3 设置头信息
for (var i in this.opt.headers) {
this.xhr.setRequestHeader(i, this.opt.headers[i])
}
// 6.2.4 发起网络请求
this.xhr.send(this.opt.param)
// 6.2.5 超时处理
if (this.opt.timeout && this.opt.timeout > 0) {
this.xhr.timeout = this.opt.timeout
}
}
return this.defer.promise.then(res => {
return cb(res)
})
}
}
if (!win.request) {
win.request = {
get(url) {
return new _Request(url, 'GET')
if (!window.request) {
window.request = {
get(url, param) {
return new _Request(url, 'GET', param)
},
post(url) {
return new _Request(url, 'POST')
post(url, param) {
return new _Request(url, 'POST', param)
},
jsonp(url) {
return new _Request(url, 'JSONP')
upload(url, param) {
param.formType = 'form-data'
return this.post(url, param)
},
open(url, method = 'GET') {
return new _Request(url, method)
jsonp(url, param) {
return new _Request(url, 'JSONP', param)
},
open(url, method = 'GET', param) {
return new _Request(url, method, param)
},
cache: {},
cid: 0,
version: '1.1.0-normal'
version: '2.0.0-normal'
}
Anot.ui.request = request.version
}

View File

@ -8,10 +8,11 @@
'use strict'
function serialize(p, obj, q) {
var k
let k
if (Array.isArray(obj)) {
obj.forEach(function(it, i) {
k = p ? p + '[' + (Array.isArray(it) ? i : '') + ']' : i
k = p ? `${p}[${Array.isArray(it) ? i : ''}]` : i
// k = p ? p + '[' + (Array.isArray(it) ? i : '') + ']' : i
if (typeof it === 'object') {
serialize(k, it, q)
} else {
@ -19,8 +20,9 @@ function serialize(p, obj, q) {
}
})
} else {
for (var i in obj) {
k = p ? p + '[' + i + ']' : i
for (let i in obj) {
k = p ? `${p}[${i}]` : i
// k = p ? p + '[' + i + ']' : i
if (typeof obj[i] === 'object') {
serialize(k, obj[i], q)
} else {
@ -30,12 +32,12 @@ function serialize(p, obj, q) {
}
}
var toS = Object.prototype.toString
var doc = window.document
var encode = encodeURIComponent
var decode = decodeURIComponent
const toS = Object.prototype.toString
const doc = window.document
const encode = encodeURIComponent
const decode = decodeURIComponent
var TagHooks = function() {
const TagHooks = function() {
this.option = doc.createElement('select')
this.thead = doc.createElement('table')
this.td = doc.createElement('tr')
@ -49,37 +51,34 @@ var TagHooks = function() {
this.optgroup = this.option
this.tbody = this.tfoot = this.colgroup = this.caption = this.thead
this.th = this.td
}
var Format = function() {
var _this = this
this.tagHooks = new TagHooks()
'circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use'.replace(
/,/g,
function(m) {
_this.tagHooks[m] = _this.tagHooks.g //处理svg
m => {
this[m] = this.g //处理svg
}
)
}
this.rtagName = /<([\w:]+)/
this.rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi
this.scriptTypes = {
const Helper = {
tagHooks: new TagHooks(),
rtagName: /<([\w:]+)/,
rxhtml: /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
scriptTypes: {
'text/javascript': 1,
'text/ecmascript': 1,
'application/ecmascript': 1,
'application/javascript': 1
}
this.rhtml = /<|&#?\w+;/
},
rhtml: /<|&#?\w+;/
}
Format.prototype = {
export default {
parseJS: function(code) {
code = (code + '').trim()
if (code) {
if (code.indexOf('use strict') === 1) {
var script = doc.createElement('script')
let script = doc.createElement('script')
script.text = code
doc.head.appendChild(script).parentNode.removeChild(script)
} else {
@ -105,27 +104,29 @@ Format.prototype = {
return xml
},
parseHTML: function(html) {
var fragment = doc.createDocumentFragment().cloneNode(false)
let fragment = doc.createDocumentFragment().cloneNode(false)
if (typeof html !== 'string') return fragment
if (typeof html !== 'string') {
return fragment
}
if (!this.rhtml.test(html)) {
if (!Helper.rhtml.test(html)) {
fragment.appendChild(document.createTextNode(html))
return fragment
}
html = html.replace(this.rxhtml, '<$1></$2>').trim()
var tag = (this.rtagName.exec(html) || ['', ''])[1].toLowerCase()
var wrap = this.tagHooks[tag] || this.tagHooks._default
var firstChild = null
html = html.replace(Helper.rxhtml, '<$1></$2>').trim()
let tag = (Helper.rtagName.exec(html) || ['', ''])[1].toLowerCase()
let wrap = Helper.tagHooks[tag] || Helper.tagHooks._default
let firstChild = null
//使用innerHTML生成的script节点不会触发请求与执行text属性
wrap.innerHTML = html
var script = wrap.getElementsByTagName('script')
let script = wrap.getElementsByTagName('script')
if (script.length) {
for (var i = 0, el; (el = script[i++]); ) {
if (this.scriptTypes[el.type]) {
var tmp = doc.createElement('script').cloneNode(false)
for (let i = 0, el; (el = script[i++]); ) {
if (Helper.scriptTypes[el.type]) {
let tmp = doc.createElement('script').cloneNode(false)
el.attributes.forEach(function(attr) {
tmp.setAttribute(attr.name, attr.value)
})
@ -142,11 +143,15 @@ Format.prototype = {
return fragment
},
param: function(obj) {
if (!obj || typeof obj === 'string' || typeof obj === 'number') return obj
if (!obj || typeof obj === 'string' || typeof obj === 'number') {
return obj
}
var arr = []
var q = function(k, v) {
if (/native code/.test(v)) return
let arr = []
let q = function(k, v) {
if (/native code/.test(v)) {
return
}
v = typeof v === 'function' ? v() : v
v = toS.call(v) !== '[object File]' ? encode(v) : v
@ -154,18 +159,20 @@ Format.prototype = {
arr.push(encode(k) + '=' + v)
}
if (typeof obj === 'object') serialize('', obj, q)
if (typeof obj === 'object') {
serialize('', obj, q)
}
return arr.join('&')
},
parseForm: function(form) {
var data = {}
for (var i = 0, field; (field = form.elements[i++]); ) {
let data = {}
for (let i = 0, field; (field = form.elements[i++]); ) {
switch (field.type) {
case 'select-one':
case 'select-multiple':
if (field.name.length && !field.disabled) {
for (var j = 0, opt; (opt = field.options[j++]); ) {
for (let j = 0, opt; (opt = field.options[j++]); ) {
if (opt.selected) {
data[field.name] = opt.value || opt.text
}
@ -193,18 +200,5 @@ Format.prototype = {
}
}
return data
},
merge: function(a, b) {
if (typeof a !== 'object' || typeof b !== 'object')
throw new TypeError('argument must be an object')
if (Object.assign) return Object.assign(a, b)
for (var i in b) {
a[i] = b[i]
}
return a
}
}
export default new Format()