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

重构request插件

old
宇天 2018-03-26 03:08:47 +08:00
parent adc8d73365
commit fe90703b85
1 changed files with 443 additions and 374 deletions

View File

@ -1,95 +1,115 @@
/** /**
* Request组件, modern版, 支持IE9+,chrome,FF
* @authors yutent (yutent@doui.cc)
* @date 2016-11-27 13:08:40
* *
* @authors yutent (yutent@doui.cc)
* @date 2018-03-25 23:59:13
* @version $Id$
*/ */
'use strict' 'use strict'
import 'promise/index' import 'promise/index'
import Format from './lib/format' import Format from './lib/format'
var _request = function(url, protocol) { // 本地协议/头 判断正则
this.transport = true const rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
protocol = (protocol + '').trim().toUpperCase() const rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/gm
this.xhr = Xhr() const encode = encodeURIComponent
this.defer = Promise.defer() const decode = decodeURIComponent
this.opt = { const toS = Object.prototype.toString
url: (url + '').trim(), const win = window
type: protocol || 'GET', const doc = win.document
form: '',
data: {}, const noop = function(e, res) {
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) {
this.defer.resolve(res) this.defer.resolve(res)
} }
const Xhr = function() {
return new XMLHttpRequest()
}
const supportCors = 'withCredentials' in Xhr()
// ----------------------------- let isLocal = false
// 本地协议判断正则
var rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
var isLocal = false
try { try {
isLocal = rlocalProtocol.test(location.ptyperotocol) isLocal = rlocalProtocol.test(location.ptyperotocol)
} catch (e) {} } catch (e) {}
var rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/gm let originAnchor = doc.createElement('a')
originAnchor.href = location.href
// ----------------- 一些兼容性预处理 -------------------- const noBodyMethods = ['GET', 'HEAD']
const error = {
win.Xhr = function() { 10001: 'argument url is required',
return new XMLHttpRequest() 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...',
10200: 'ok',
10204: 'no content',
10304: 'not modified',
10500: 'Internal Server Error',
10504: 'Connected timeout',
form: 'application/x-www-form-urlencoded; charset=UTF-8',
json: 'application/json; charset=UTF-8',
text: 'text/plain; charset=UTF-8'
} }
var supportCors = 'withCredentials' in Xhr()
// --------------------------------------------------------- const convert = {
// -------------------- request 模块开始 -------------------- text(val) {
// ---------------------------------------------------------
var requestConvert = {
text: function(val) {
return val return val
}, },
xml: function(val, xml) { xml(val, xml) {
return xml !== undefined ? xml : Format.parseXML(val) return xml !== undefined ? xml : Format.parseXML(val)
}, },
html: function(val) { html(val) {
return Format.parseHTML(val) return Format.parseHTML(val)
}, },
json: function(val) { json(val) {
return JSON.parse(val) return JSON.parse(val)
}, },
script: function(val) { script(val) {
return Format.parseJS(val) return Format.parseJS(val)
}, },
jsonp: function(name) { jsonp(name) {
var json = request.cache[name] var json = request.cache[name]
delete request.cache[name] delete request.cache[name]
return json return json
} }
} }
var requestExtend = {
formData: function() { class _Request {
constructor(url = '', method = 'GET') {
if (!url) {
throw new Error(error[10001])
}
method = method.toUpperCase()
this.transport = Object.create(null)
this.xhr = Xhr()
this.defer = Promise.defer()
this.opt = {
url,
method,
form: null,
data: {},
headers: {},
timeoutID: 0,
uuid: Math.random()
.toString(16)
.slice(2)
}
}
_formData() {
if (this.opt.form) { if (this.opt.form) {
var data = Format.parseForm(this.opt.form) let data = Format.parseForm(this.opt.form)
Format.merge(this.opt.data, data) Format.merge(this.opt.data, data)
} }
var form = new FormData() let form = new FormData()
for (var i in this.opt.data) { for (let i in this.opt.data) {
var el = this.opt.data[i] let el = this.opt.data[i]
if (Array.isArray(el)) { if (Array.isArray(el)) {
el.forEach(function(it) { el.forEach(function(it) {
form.append(i + '[]', it) form.append(i + '[]', it)
@ -99,60 +119,65 @@ var requestExtend = {
} }
} }
return form return form
},
jsonp: function(jsonpcallback) {
win[jsonpcallback] = function(val) {
delete win[jsonpcallback]
request.cache[jsonpcallback] = val
} }
},
dispatch: function(self) {
if (!this.transport) return this.defer.reject('Request pending...')
var _this = this, _jsonp(cb) {
result = { 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: { response: {
url: this.opt.url, url: this.opt.url,
headers: { 'content-type': '' } headers: { 'content-type': '' }
}, },
request: { request: {
url: this.opt.url, url: this.opt.url,
headers: _this.opt.headers headers: this.opt.headers
}, },
status: self === null ? 504 : 200, status: isTimeout === null ? 504 : 200,
statusText: self === null ? 'Connected timeout' : 'ok', statusText: isTimeout === null ? 'Connected timeout' : 'ok',
text: '', text: '',
body: '', body: '',
error: null error: null
} }
//状态为4,既已成功, 则清除超时 //状态为4,既已成功, 则清除超时
clearTimeout(_this.opt.timeoutID) clearTimeout(this.opt.timeoutID)
if (typeof this.transport === 'object' && this.opt.type === 'JSONP') { if (this.transport.nodeType && this.opt.method === 'JSONP') {
//移除script //移除script
// this.transport.parentNode.removeChild(this.transport); this.transport.parentNode.removeChild(this.transport)
//超时返回 //超时返回
if (self !== null) { if (!isTimeout) {
var exec = let exec =
!this.transport.readyState || !this.transport.readyState ||
this.transport.readyState === 'loaded' || this.transport.readyState === 'loaded' ||
this.transport.readyState === 'complete' this.transport.readyState === 'complete'
if (exec) { if (exec) {
result.body = requestConvert.jsonp(this.opt.data.callback) result.body = convert.jsonp(this.opt.data.callback)
result.text = JSON.stringify(result.body) result.text = JSON.stringify(result.body)
} }
} }
this.defer.resolve(result)
this.callback(result.error, result)
} else { } else {
//成功的回调 //成功的回调
var isSucc = self let isSucc = isTimeout
? (self.status >= 200 && self.status < 300) || self.status === 304 ? false
: false, : (this.transport.status >= 200 && this.transport.status < 300) ||
headers = (self && self.getAllResponseHeaders().split('\n')) || [] this.transport.status === 304
let headers =
(!isTimeout && this.transport.getAllResponseHeaders().split('\n')) || []
//处理返回的Header //处理返回的Header
headers.forEach(function(it, i) { headers.forEach(function(it, i) {
@ -166,21 +191,18 @@ var requestExtend = {
}) })
if (isSucc) { if (isSucc) {
result.status = self.status result.status = this.transport.status
if (result.status === 204) { if (result.status === 204) {
result.statusText = 'no content' result.statusText = error[10204]
} else if (result.status === 304) { } else if (result.status === 304) {
result.statusText = 'not modified' result.statusText = error[10304]
} }
} else { } else {
result.status = self === null ? 504 : self.status || 500 result.status = isTimeout ? 504 : this.transport.status || 500
result.statusText = result.statusText = isTimeout
self === null ? error[10504]
? 'Connected timeout' : this.transport.statusText || error[10500]
: self.statusText || 'Internal Server Error' result.error = new Error(result.statusText)
result.error = Format.merge(new Error(result.statusText), {
status: result.status
})
} }
try { try {
@ -190,173 +212,201 @@ var requestExtend = {
) || ['text'] ) || ['text']
dataType = dataType[0].toLowerCase() dataType = dataType[0].toLowerCase()
result.text = (self && (self.responseText || self.responseXML)) || '' result.text = isTimeout
result.body = requestConvert[dataType]( ? ''
: this.transport.responseText || this.transport.responseXML
result.body = convert[dataType](
result.text, result.text,
self && self.responseXML !isTimeout && this.transport.responseXML
) )
} catch (err) { } catch (err) {
result.error = err result.error = err
result.statusText = 'parse error' result.statusText = error[10012]
} }
_this.callback(result.error, result) this.defer.resolve(result)
} }
delete _this.defer delete this.transport
delete _this.transport delete this.opt
delete _this.opt delete this.xhr
delete _this.xhr
} }
}
// 设置表单类型, 支持2种, form/json // 设置表单类型, 支持3种, form(即x-www-form-urlencoded)/json/text
_requestp.type = function(t) { type(type) {
if (this.opt.formType === 'form-data') return this // 如果已经是带附件上传的表单,不再支持修改表单类型
if (this.opt.formType === 'form-data') {
return this
}
this.opt.formType = t || 'form' this.opt.formType = type || 'form'
if (t === 'form' || this.opt.type === 'GET')
this.set('content-type', 'application/x-www-form-urlencoded; charset=UTF-8') // 不是POST方式, 强制为x-www-form-urlencoded
else this.set('content-type', 'application/json; charset=UTF-8') 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)
}
return this 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() set(key, val) {
// 已经发起请求之后,不再允许追加头信息了
if (val === undefined) delete this.opt.headers[k] if (!this.transport) {
else this.opt.headers[k] = val
} else {
throw new Error(
'arguments must be string/object, but [' + typeof k + '] given'
)
}
return this return this
} }
//设置请求参数 let obj = {}
_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 = {}
Format.merge(this.opt.data, k)
} else {
if (typeof k === 'string') {
if (arguments.length === 1) { if (arguments.length === 1) {
if (this.opt.data) throw new Error('invalid param in function send') if (typeof key !== 'object') {
this.defer.reject(error[10002])
this.opt.data = k return this
} else { }
if (this.opt.data && typeof this.opt.data === 'string') obj = key
throw new Error('param can not be string and object at the same time') } else if (arguments.length === 2) {
if (typeof key === 'string' && val !== undefined) {
if (!this.opt.data) this.opt.data = {} obj[key] = val
this.opt.data[k] = val
} }
} else { } else {
throw new Error( this.defer.reject(error[10002])
'argument of send must be string/object, but [' + typeof k + '] given' 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 return this
} }
//该方法用于 form-data类型的post请求的参数设置 //该方法用于 form-data类型的post请求的参数设置
_requestp.field = function(k, val) { field(key, val) {
if (!this.transport) return this if (!this.transport) {
return this
}
// 此类型优先级最高 // 此类型优先级最高
this.opt.formType = 'form-data' this.opt.formType = 'form-data'
this.opt.type = 'POST' this.opt.method = 'POST'
if (!this.opt.data || (this.opt.data && typeof this.opt.data !== 'object')) if (!this.opt.data || typeof this.opt.data !== 'object') {
this.opt.data = {} this.opt.data = {}
}
if (arguments.length === 1 && typeof k === 'object') { if (arguments.length === 1 && typeof key === 'object') {
Format.merge(this.opt.data, k) Format.merge(this.opt.data, key)
} else if (arguments.length === 2) { } else if (arguments.length === 2) {
this.opt.data[k] = val this.opt.data[key] = val
} else { } else {
throw new TypeError( this.defer.reject(error[10006])
'argument must be an object, but ' + typeof k + ' given'
)
} }
return this return this
} }
//设置缓存 //设置缓存
_requestp.cache = function(t) { cache(bool) {
if (!this.transport) return if (!this.transport) {
return this
}
if (this.opt.type === 'GET') this.opt.cache = !!t if (noBodyMethods.indexOf(this.opt.method) > -1) {
this.opt.cache = !!bool
}
return this return this
} }
//取消网络请求 //取消网络请求
_requestp.abort = function() { abort() {
delete this.transport delete this.transport
if (!this.opt.form) this.xhr.abort() if (!this.opt.form) {
this.xhr.abort()
}
return this return this
} }
//超时设置, 单位毫秒 //超时设置, 单位毫秒
_requestp.timeout = function(time) { timeout(time) {
if (typeof time !== 'number' || time < 1) return this if (typeof time !== 'number' || time < 1) {
return this
}
this.opt.timeout = time this.opt.timeout = time
return this return this
} }
_requestp.form = function(form) { form(form) {
if (typeof form === 'object' && form.nodeName === 'FORM') { if (typeof form === 'object' && form.nodeName === 'FORM') {
this.opt.type = 'POST' this.opt.method = 'POST'
this.opt.form = form this.opt.form = form
} }
return this return this
} }
then(cb) {
if (typeof cb !== 'function') {
this.defer.reject(error[10011])
return this.defer.promise
}
var originAnchor = doc.createElement('a')
originAnchor.href = location.href
_requestp.end = function(callback) {
var _this = this
// 回调已执行, 或已取消, 则直接返回, 防止重复执行 // 回调已执行, 或已取消, 则直接返回, 防止重复执行
if (!this.transport) return this if (!this.transport) {
return this.defer.promise
if (!this.opt.url) throw new Error('Invalid request url') }
Format.merge(this, requestExtend)
this.callback = callback || noop.bind(this)
// ------------------------------------------
// 1. url规范化 // 1. url规范化
// ------------------------------------------
this.opt.url = this.opt.url this.opt.url = this.opt.url
.replace(/#.*$/, '') .replace(/#.*$/, '')
.replace(/^\/\//, location.protocol + '//') .replace(/^\/\//, location.protocol + '//')
// ------------------------------------------
// 2. 处理跨域 // 2. 处理跨域
// ------------------------------------------
// 2.1 判断是否跨域
if (typeof this.opt.crossDomain !== 'boolean') { if (typeof this.opt.crossDomain !== 'boolean') {
var anchor = doc.createElement('a') var anchor = doc.createElement('a')
try { try {
@ -374,120 +424,139 @@ _requestp.end = function(callback) {
} }
} }
// 2.1 进一步处理跨域配置 // 2.2 进一步处理跨域配置
if (this.opt.type === 'JSONP') { if (this.opt.method === 'JSONP') {
//如果没有跨域自动转回xhr GET //如果没有跨域自动转回xhr GET
if (!this.opt.crossDomain) { if (!this.opt.crossDomain) {
this.opt.type = 'GET' this.opt.method = 'GET'
} else { } else {
this.opt.data['callback'] = this.opt.data['callback'] =
this.opt.data['callback'] || 'jsonp' + request.cid++ this.opt.data['callback'] || 'jsonp' + request.cid++
this.jsonp(this.opt.data['callback']) //创建临时处理方法 this._jsonp(this.opt.data['callback']) //创建临时处理方法
} }
} }
// 2.2 如果不是跨域请求则自动加上一条header信息用以标识这是ajax请求
// 2.3 如果不是跨域请求则自动加上一条header信息用以标识这是ajax请求
if (!this.opt.crossDomain) { if (!this.opt.crossDomain) {
this.set('X-Requested-With', 'XMLHttpRequest') this.set('X-Requested-With', 'XMLHttpRequest')
} else { } else {
supportCors && (this.xhr.withCredentials = true) supportCors && (this.xhr.withCredentials = true)
} }
// 3. data转字符串 // ------------------------------------------
// 3. 解析 data
// ------------------------------------------
this.opt.param = Format.param(this.opt.data) this.opt.param = Format.param(this.opt.data)
// 4. 设置Content-Type类型, 默认x-www-form-urlencoded // ------------------------------------------
if (!this.opt.formType) this.type('form') // 4. 修正默认表单类型
// ------------------------------------------
if (!this.opt.formType) {
this.type('form')
}
// 5.处理GET请求 // ------------------------------------------
this.opt.hasContent = this.opt.type === 'POST' //是否为post请求 // 5. 根据method类型,处理body
if (!this.opt.hasContent) { // ------------------------------------------
let hasBody = noBodyMethods.indexOf(this.opt.method) < 0 //是否为post请求
if (!hasBody) {
//GET请求直接把参数拼接到url上 //GET请求直接把参数拼接到url上
if (this.opt.param) { if (this.opt.param) {
this.opt.url += (/\?/.test(this.opt.url) ? '&' : '?') + this.opt.param this.opt.url += (/\?/.test(this.opt.url) ? '&' : '?') + this.opt.param
} }
//加随机值,避免缓存 //加随机值,避免缓存
if (this.opt.cache === false) if (this.opt.cache === false) {
this.opt.url += this.opt.url +=
(/\?/.test(this.opt.url) ? '&' : '?') + '_=' + Math.random() (/\?/.test(this.opt.url) ? '&' : '?') + '_=' + Math.random()
}
} else { } else {
if (this.opt.formType === 'form-data') { if (this.opt.formType === 'form-data') {
delete this.opt.headers['content-type'] delete this.opt.headers['content-type']
this.opt.param = this.formData() this.opt.param = this._formData()
} else if (this.opt.formType !== 'form') { } else if (this.opt.formType !== 'form') {
this.opt.param = JSON.stringify(this.opt.data) if (typeof this.opt.data === 'object') {
this.opt.data = JSON.stringify(this.opt.data)
}
this.opt.param = this.opt.data
} }
} }
//jsonp // ------------------------------------------
if (this.opt.type === 'JSONP') { // 6. 构造并发起请求
// ------------------------------------------
// 6.1 jsonp
if (this.opt.method === 'JSONP') {
// 6.1.1 构造script并插入
this.transport = doc.createElement('script') this.transport = doc.createElement('script')
this.transport.onerror = this.transport.onload = function() { this.transport.onerror = this.transport.onload = () => {
_this.dispatch(_this.transport) this._dispatch()
} }
this.transport.src = this.opt.url this.transport.src = this.opt.url
doc.head.insertBefore(this.transport, doc.head.firstChild) doc.head.insertBefore(this.transport, doc.head.firstChild)
//6. 超时处理 // 6.1.2 超时处理
if (this.opt.timeout && this.opt.timeout > 0) { if (this.opt.timeout && this.opt.timeout > 0) {
this.opt.timeoutID = setTimeout(function() { this.opt.timeoutID = setTimeout(() => {
_this.transport.onerror = _this.transport.onload = null this.transport.onerror = this.transport.onload = null
_this.dispatch(null) this._dispatch(true)
}, this.opt.timeout) }, this.opt.timeout)
} }
} else { } else {
this.xhr.onreadystatechange = function(ev) { this.transport = this.xhr
if (_this.opt.timeout && _this.opt.timeout > 0) { // 6.2 非jsonp
_this.opt['time' + this.readyState] = ev.timeStamp // 6.2.1 监听http状态
if (this.readyState === 4) { this.xhr.onreadystatechange = ev => {
_this.opt.isTimeout = if (this.opt.timeout && this.opt.timeout > 0) {
_this.opt.time4 - _this.opt.time1 > _this.opt.timeout 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.readyState !== 4) { if (this.xhr.readyState !== 4) {
return return
} }
_this.dispatch(_this.opt.isTimeout ? null : _this.xhr) this._dispatch(this.opt.isTimeout)
} }
// 6. 初始化xhr提交 // 6.2.2 初始化xhr提交
this.xhr.open(this.opt.type, this.opt.url, true) this.xhr.open(this.opt.method, this.opt.url, true)
// 7. 设置头信息 // 6.2.3 设置头信息
for (var i in this.opt.headers) { for (var i in this.opt.headers) {
if (this.opt.headers[i]) this.xhr.setRequestHeader(i, this.opt.headers[i]) this.xhr.setRequestHeader(i, this.opt.headers[i])
} }
// 8. 发起网络请求 // 6.2.4 发起网络请求
_this.xhr.send(_this.opt.param) this.xhr.send(this.opt.param)
//超时处理 // 6.2.5 超时处理
if (this.opt.timeout && this.opt.timeout > 0) { if (this.opt.timeout && this.opt.timeout > 0) {
this.xhr.timeout = this.opt.timeout this.xhr.timeout = this.opt.timeout
} }
} }
return this.defer.promise
}
// ---------------------- end ------------------------ return this.defer.promise.then(res => {
return cb(res)
})
}
}
if (!win.request) { if (!win.request) {
win.request = { win.request = {
get: function(url) { get(url) {
if (!url) throw new Error('argument url is required') return new _Request(url, 'GET')
return new _request(url, 'GET')
}, },
post: function(url) { post(url) {
if (!url) throw new Error('argument url is required') return new _Request(url, 'POST')
return new _request(url, 'POST')
}, },
jsonp: function(url) { jsonp(url) {
if (!url) throw new Error('argument url is required') return new _Request(url, 'JSONP')
},
return new _request(url, 'JSONP') open(url, method = 'GET') {
return new _Request(url, method)
}, },
cache: {}, cache: {},
cid: 0, cid: 0,