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
wcui/js/lib/request/normal.js

741 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* Request组件, modern版, 支持IE9+,chrome,FF
* @authors yutent (yutent@doui.cc)
* @date 2016-11-27 13:08:40
*
*/
"use strict";
define(function(){
var _request = function(url, protocol){
this.transport = true
protocol = (protocol + '').trim().toUpperCase()
this.xhr = Xhr()
this.opt = {
url: (url + '').trim(),
type: protocol || 'GET',
form: '',
data: {},
headers: {},
timeoutID: 0,
uuid: Math.random().toString(16).substr(2)
}
},
_requestp = _request.prototype,
toS = Object.prototype.toString,
win = window,
doc = win.document,
encode = encodeURIComponent,
decode = decodeURIComponent,
noop = function(e, res){
if(e)
throw new Error(e + '')
};
// -----------------------------
// 本地协议判断正则
var rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
var isLocal = false
try{
isLocal = rlocalProtocol.test(location.protocol)
}catch(e){}
var rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg
// ----------------- 一些兼容性预处理 --------------------
win.Xhr = function(){
return new XMLHttpRequest()
}
// var supportCors = 'withCredentials' in Xhr()
// ------------------- 几个解释方法 -----------------------
var Format = function(){
this.tagHooks = new function(){
this.option = doc.createElement('select')
this.thead = doc.createElement('table')
this.td = doc.createElement('tr')
this.area = doc.createElement('map')
this.tr = doc.createElement('tbody')
this.col = doc.createElement('colgroup')
this.legend = doc.createElement('fieldset')
this._default = doc.createElement('div')
this.g = doc.createElementNS('http://www.w3.org/2000/svg', 'svg')
this.optgroup = this.option
this.tbody = this.tfoot = this.colgroup = this.caption = this.thead
this.th = this.td
};
var _this = this
'circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use'.replace(/,/g, function(m){
_this.tagHooks[m] = _this.tagHooks.g //处理svg
})
this.rtagName = /<([\w:]+)/
this.rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig
this.scriptTypes = {
'text/javascript': 1,
'text/ecmascript': 1,
'application/ecmascript': 1,
'application/javascript': 1
}
this.rhtml = /<|&#?\w+;/
}
function serialize(p, obj, q){
var k
if(Array.isArray(obj)){
obj.forEach(function(it, i){
k = p ? (p + '[' + (Array.isArray(it) ? i : '') + ']') : i
if(typeof it === 'object'){
serialize(k, it, q)
}else{
q(k, it)
}
})
}else{
for(var i in obj){
k = p ? (p + '[' + i + ']') : i
if(typeof obj[i] === 'object'){
serialize(k, obj[i], q)
}else{
q(k, obj[i])
}
}
}
}
Format.prototype = {
parseJS: function(code){
code = (code + '').trim()
if(code){
if(code.indexOf('use strict') === 1){
var script = doc.createElement('script')
script.text = code
doc.head
.appendChild(script)
.parentNode
.removeChild(script)
}else{
eval(code)
}
}
},
parseXML: function(data, xml, tmp){
try{
tmp = new DOMParser();
xml = tmp.parseFromString(data, 'text/xml');
}catch(e){
xml = void 0;
}
if(!xml ||
!xml.documentElement ||
xml.getElementsByTagName('parsererror').length){
console.error('Invalid XML: ' + data)
}
return xml
},
parseHTML: function (html){
var fragment = (doc.createDocumentFragment()).cloneNode(false)
if(typeof html !== 'string')
return fragment
if(!this.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
//使用innerHTML生成的script节点不会触发请求与执行text属性
wrap.innerHTML = html
var 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)
el.attributes.forEach(function(attr){
tmp.setAttribute(attr.name, attr.value)
})
tmp.text = el.text
el.parentNode.replaceChild(tmp, el)
}
}
}
while(firstChild = wrap.firstChild){
fragment.appendChild(firstChild)
}
return fragment
},
param: function(obj){
if(!obj || typeof obj === 'string' || typeof obj === 'number')
return obj
var arr = []
var 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
arr.push(encode(k) + '=' + v)
}
if(typeof obj === 'object')
serialize('', obj, q)
return arr.join('&')
},
parseForm: function(form){
var data = {}
for(var 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++];){
if(opt.selected){
data[field.name] = opt.value || opt.text
}
}
}
break;
case 'file':
if(field.name.length && !field.disabled){
data[field.name] = field.files[0]
}
break;
case undefined:
case 'submit':
case 'reset':
case 'button':
break; //按钮啥的, 直接忽略
case 'radio':
case 'checkbox':
// 只处理选中的
if(!field.checked)
break;
default:
if(field.name.length && !field.disabled){
data[field.name] = field.value
}
}
}
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
}
}
var F = new Format()
// ---------------------------------------------------------
// -------------------- request 模块开始 --------------------
// ---------------------------------------------------------
var requestConvert = {
text: function(val){
return val
},
xml: function(val, xml){
return xml !== undefined ? xml : F.parseXML(val)
},
html: function(val){
return F.parseHTML(val)
},
json: function(val){
return JSON.parse(val)
},
script: function(val){
return F.parseJS(val)
},
jsonp: function(name){
var json = request.cache[name]
delete request.cache[name];
return json
}
}
var requestExtend = {
formData: function(){
if(this.opt.form){
var data = F.parseForm(this.opt.form)
F.merge(this.opt.data, data)
}
var form = new FormData()
for(var i in this.opt.data){
var el = this.opt.data[i]
if(Array.isArray(el)){
el.forEach(function(it){
form.append(i + '[]', it)
})
}else{
form.append(i, this.opt.data[i])
}
}
return form
},
jsonp: function(jsonpcallback){
win[jsonpcallback] = function(val){
delete win[jsonpcallback]
request.cache[jsonpcallback] = val
}
},
dispatch: function(self){
if(!this.transport)
return
var _this = this,
result = {
response: {
url: this.opt.url,
headers: {'content-type': ''}
},
request: {
url: this.opt.url,
headers: _this.opt.headers
},
status: self === null ? 504 : 200,
statusText: self === null ? 'Connected timeout' : 'ok',
text: '',
body: '',
error: null
};
//状态为4,既已成功, 则清除超时
clearTimeout(_this.opt.timeoutID);
if(typeof this.transport === 'object'
&& this.opt.type === 'JSONP'){
//移除script
// this.transport.parentNode.removeChild(this.transport);
//超时返回
if(self !== null){
var exec = !this.transport.readyState
|| this.transport.readyState === 'loaded'
|| this.transport.readyState === 'complete';
if(exec){
result.body = requestConvert.jsonp(this.opt.data.callback)
result.text = JSON.stringify(result.body)
}
}
this.callback(result.error, result)
}else{
//成功的回调
var isSucc = self ? ((self.status >= 200 && self.status < 300) || self.status === 304) : false,
headers = self && self.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()
}
});
if(isSucc){
result.status = self.status
if(result.status === 204){
result.statusText = 'no content'
}else if(result.status === 304){
result.statusText = 'not modified'
}
}else{
result.status = self === null ? 504 : (self.status || 500)
result.statusText = self === null ? 'Connected timeout' : (self.statusText || 'Internal Server Error')
result.error = F.merge(new Error(result.statusText), {status: result.status})
}
try{
//处理返回的数据
var dataType = result.response.headers['content-type'].match(/json|xml|script|html/i) || ['text']
dataType = dataType[0].toLowerCase()
result.text = self && (self.responseText || self.responseXML) || ''
result.body = requestConvert[dataType](result.text, self && self.responseXML)
}catch(err){
result.error = err
result.statusText = 'parse error'
}
_this.callback(result.error, result)
}
delete _this.transport;
delete _this.opt
delete _this.xhr
}
}
// 设置表单类型, 支持2种, form/json
_requestp.type = function(t){
if(this.opt.formType === 'form-data')
return this
this.opt.formType = t || 'form'
if(t === 'form' || this.opt.type === 'GET')
this.set('content-type', 'application/x-www-form-urlencoded; charset=UTF-8')
else
this.set('content-type', 'application/json; charset=UTF-8')
return this
}
//设置头信息
_requestp.set = function(k, val){
if(!this.transport)
return
if(typeof k === 'object'){
for(var i in k){
i = i.toLowerCase()
this.opt.headers[i] = k[i]
}
}else if(typeof k === 'string'){
if(arguments.length < 2)
throw new Error('2 arguments required')
// 全转小写,避免重复写入
k = k.toLowerCase()
if(val === undefined)
delete this.opt.headers[k]
else
this.opt.headers[k] = val
}else{
throw new Error('arguments must be string/object, but [' + (typeof k) + '] given')
}
return this
}
//设置请求参数
_requestp.send = function(k, val){
if(!this.transport)
return
// 1. send方法可以多次调用, 但必须保证格式一致
// 2. 2次圴提交纯字符串也会抛出异常
if(typeof k === 'object'){
if(this.opt.data && (typeof this.opt.data === 'string'))
throw new Error('param can not be string and object at the same time')
if(!this.opt.data)
this.opt.data = {}
F.merge(this.opt.data, k)
}else{
if(typeof k === 'string'){
if(arguments.length === 1){
if(this.opt.data)
throw new Error('invalid param in function send')
this.opt.data = k
}else{
if(this.opt.data && (typeof this.opt.data === 'string'))
throw new Error('param can not be string and object at the same time')
if(!this.opt.data)
this.opt.data = {}
this.opt.data[k] = val
}
}else{
throw new Error('argument of send must be string/object, but [' + (typeof k) + '] given')
}
}
return this
}
//该方法用于 form-data类型的post请求的参数设置
_requestp.field = function(k, val){
if(!this.transport)
return
// 此类型优先级最高
this.opt.formType = 'form-data'
if(!this.opt.data || (this.opt.data && typeof this.opt.data !== 'object'))
this.opt.data = {}
if(arguments.length === 1 && typeof k === 'object'){
F.merge(this.opt.data, k)
}else if(arguments.length === 2){
this.opt.data[k] = val
}else{
throw new TypeError('argument must be an object, but ' + (typeof k) + ' given')
}
return this
}
//设置缓存
_requestp.cache = function(t){
if(!this.transport)
return
if(this.opt.type === 'GET')
this.opt.cache = !!t
return this
}
//取消网络请求
_requestp.abort = function(){
delete this.transport
if(!this.opt.form)
this.xhr.abort()
return this
}
//超时设置, 单位毫秒
_requestp.timeout = function(time){
if(typeof time !== 'number' || time < 1)
return this
this.opt.timeout = time
return this
}
_requestp.form = function(form){
if(typeof form === 'object' && form.nodeName === 'FORM'){
this.opt.type = 'POST'
this.opt.form = form
}
return this
}
var originAnchor = doc.createElement('a');
originAnchor.href = location.href;
_requestp.end = function(callback){
var _this = this;
// 回调已执行, 或已取消, 则直接返回, 防止重复执行
if(!this.transport)
return
if(!this.opt.url)
throw new Error('Invalid request url')
F.merge(this, requestExtend)
this.callback = callback || noop
// 1. url规范化
this.opt.url = this.opt.url.replace(/#.*$/, '').replace(/^\/\//, location.protocol + '//')
// 2. 处理跨域
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
}
}
// 2.1 进一步处理跨域配置
if(this.opt.type === 'JSONP'){
//如果没有跨域自动转回xhr GET
if(!this.opt.crossDomain){
this.opt.type = 'GET';
}else{
this.opt.data['callback'] = this.opt.data['callback'] || ('jsonp' + request.cid++);
this.jsonp(this.opt.data['callback']); //创建临时处理方法
}
}
// 2.2 如果不是跨域请求则自动加上一条header信息用以标识这是ajax请求
if(!this.opt.crossDomain){
this.set('X-Requested-With', 'XMLHttpRequest')
}
// 3. data转字符串
this.opt.param = F.param(this.opt.data)
// 4. 设置Content-Type类型, 默认x-www-form-urlencoded
if(!this.opt.formType)
this.type('form')
// 5.处理GET请求
this.opt.hasContent = this.opt.type === 'POST' //是否为post请求
if(!this.opt.hasContent){
//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'){
this.opt.param = JSON.stringify(this.opt.data)
}
}
//jsonp
if(this.opt.type === 'JSONP'){
this.transport = doc.createElement('script')
this.transport.onerror = this.transport.onload = function(){
_this.dispatch(_this.transport)
}
this.transport.src = this.opt.url
doc.head.insertBefore(this.transport, doc.head.firstChild)
//6. 超时处理
if(this.opt.timeout && this.opt.timeout > 0){
this.opt.timeoutID = setTimeout(function(){
_this.transport.onerror = _this.transport.onload = null
_this.dispatch(null)
}, this.opt.timeout)
}
}else{
this.xhr.onreadystatechange = function(ev){
if(_this.opt.timeout && _this.opt.timeout > 0){
_this.opt['time' + this.readyState] = ev.timeStamp
if(this.readyState === 4){
_this.opt.isTimeout = _this.opt.time4 - _this.opt.time1 > _this.opt.timeout
}
}
if(this.readyState !== 4){
return
}
_this.dispatch(_this.opt.isTimeout ? null : _this.xhr)
}
// 6. 初始化xhr提交
this.xhr.open(this.opt.type, this.opt.url, true)
// 7. 设置头信息
for(var i in this.opt.headers){
if(this.opt.headers[i])
this.xhr.setRequestHeader(i, this.opt.headers[i])
}
// 8. 发起网络请求
_this.xhr.send(_this.opt.param)
//超时处理
if(this.opt.timeout && this.opt.timeout > 0){
this.xhr.timeout = this.opt.timeout;
}
}
}
// ---------------------- end ------------------------
if(!win.request){
win.request = {
get: function(url){
if(!url)
throw new Error('argument url is required')
return new _request(url, 'GET')
},
post: function(url){
if(!url)
throw new Error('argument url is required')
return new _request(url, 'POST')
},
jsonp: function(url){
if(!url)
throw new Error('argument url is required')
return new _request(url, 'JSONP')
},
cache: {},
cid: 0,
version: '1.0.0',
release: 'request ES5 version/1.0.0'
}
}
return request
})
wcui是一套基于`Web Components`的UI组件库, 宗旨是追求简单、实用、不花哨。
JavaScript 95.2%
CSS 4.8%