1.0.0
parent
4b91e552fe
commit
ae80130dea
63
Readme.md
63
Readme.md
|
@ -1,10 +1,69 @@
|
|||
## ajax的全新封装
|
||||
> 统一走fetch的风格。
|
||||
> 统一走fetch的风格。内置参数处理, 支持多实例。
|
||||
|
||||
|
||||
### 版本
|
||||
> 共有2个版本, 一个传统版本, 基于`XMLHttpRequest`; 另一个是新一代版本, 基于`window.fetch()`。2个版本功能基本一致, 使用上没有区别。
|
||||
> 共有2个版本, 一个传统版本, 基于`XMLHttpRequest`; 另一个是新一代版本, 基于`window.fetch()`。
|
||||
|
||||
`**注意:**`
|
||||
|
||||
由于`window.fetch()`只支持`http/https`协议, 所以在一些特殊的环境下(如electron等), 请使用传统版。
|
||||
|
||||
### 2个版本的区别
|
||||
|
||||
1. 超时的返回值不一样。fetch版没有额外处理, 全由原生返回; 传统版为处理过, 统一返回`Response对象`。
|
||||
|
||||
2. 缓存参数不一致, 传统版只有传入`no-store`才不会缓存,其他任何值都会缓存, 缓存机制由headers及浏览器机制决定。 fetch版支持完整的参数, 详见原生fetch文档。
|
||||
|
||||
3. 验证机制,传参不一样。传统版credentials为布尔值; fetch版本则是支持omit, same-origin, include。
|
||||
|
||||
|
||||
### 示例
|
||||
|
||||
```js
|
||||
import fetch from '//dist.bytedo.org/fetch/dist/index.js' // 传统版
|
||||
// import fetch from '//dist.bytedo.org/fetch/dist/next.js' // fetch版
|
||||
|
||||
|
||||
fetch('/get_list', {body: {page: 1}})
|
||||
.then(r => r.json())
|
||||
.then(list => {
|
||||
console.log(list)
|
||||
})
|
||||
|
||||
|
||||
// 创建一个新的fetch实例, 可传入新的基础域名, 和公共参数等
|
||||
var f1 = fetch.create('//192.168.1.101', {headers: {token: 123456}})
|
||||
|
||||
f1('/get_list', {body: {page: 1}})
|
||||
.then(r => r.json())
|
||||
.then(list => {
|
||||
console.log(list)
|
||||
})
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
### APIs
|
||||
|
||||
1. fetch(url[, options<Object>])
|
||||
> 发起一个网络请求, options的参数如下。 同时支持配置公共域名, 公共参数。
|
||||
|
||||
+ method<String> 默认GET, 可选GET/POST/PUT/DELETE...
|
||||
+ body<Any> 要发送的数据, 如果是GET方式, 会被自动拼接到url上
|
||||
+ cache<String> 是否缓存,
|
||||
+ credentials<String/Boolean> 是否校验
|
||||
+ signal<Object> 网络控制信号, 可用于中断请求
|
||||
+ timeout<Number> 超时时间, 默认30秒, 单位毫秒
|
||||
|
||||
```js
|
||||
fetch.BASE_URL = '//192.168.1.100'
|
||||
fetch.__INIT__ = {headers: {token: 123456}}
|
||||
|
||||
```
|
||||
|
||||
|
||||
2. fetch.create([base_url][, options<Object>])
|
||||
> 创建一个新的fetch实例
|
258
src/index.es7
258
src/index.es7
|
@ -4,9 +4,7 @@
|
|||
* @date 2020/08/03 17:05:10
|
||||
*/
|
||||
|
||||
import Format from './lib/format.js'
|
||||
|
||||
const log = console.log
|
||||
import { Format, toS } from './lib/format.js'
|
||||
|
||||
const noop = function(e, res) {
|
||||
this.defer.resolve(res)
|
||||
|
@ -30,24 +28,6 @@ const ERRORS = {
|
|||
10504: 'Connected timeout'
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Promise.defer = function() {
|
||||
var _ = {}
|
||||
_.promise = new Promise(function(y, n) {
|
||||
|
@ -57,10 +37,8 @@ Promise.defer = function() {
|
|||
return _
|
||||
}
|
||||
|
||||
class _Instance {}
|
||||
|
||||
class _Request {
|
||||
constructor(url = '', options = {}) {
|
||||
constructor(url = '', options = {}, { BASE_URL, __INIT__ }) {
|
||||
if (!url) {
|
||||
throw new Error(ERRORS[10001])
|
||||
}
|
||||
|
@ -68,9 +46,9 @@ class _Request {
|
|||
// url规范化
|
||||
url = url.replace(/#.*$/, '')
|
||||
|
||||
if (fetch.BASE_URL) {
|
||||
if (BASE_URL) {
|
||||
if (!/^([a-z]+:|\/\/)/.test(url)) {
|
||||
url = fetch.BASE_URL + url
|
||||
url = BASE_URL + url
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,18 +64,25 @@ class _Request {
|
|||
},
|
||||
body: null,
|
||||
cache: 'default',
|
||||
referrer: '',
|
||||
credentials: false, // 跨域选项,是否验证凭证
|
||||
signal: null, // 超时信号, 配置该项时, timeout不再生效
|
||||
timeout: 30000 // 超时时间, 单位毫秒, 默认30秒
|
||||
}
|
||||
|
||||
// 取消网络请求
|
||||
// this.defer.promise.abort = () => {
|
||||
// this.cancel = true
|
||||
// this.xhr.abort()
|
||||
// }
|
||||
Object.assign(this.options, fetch.__INIT__, options, { url })
|
||||
if (!options.signal) {
|
||||
var control = new AbortController()
|
||||
options.signal = control.signal
|
||||
}
|
||||
this.defer.promise.abort = function() {
|
||||
control.abort()
|
||||
}
|
||||
|
||||
if (__INIT__.headers && options.headers) {
|
||||
Object.assign(__INIT__.headers, options.headers)
|
||||
}
|
||||
|
||||
Object.assign(this.options, __INIT__, options, { url })
|
||||
|
||||
this.__next__()
|
||||
return this.defer.promise
|
||||
}
|
||||
|
@ -107,15 +92,9 @@ class _Request {
|
|||
var params = null
|
||||
var hasAttach = false // 是否有附件
|
||||
var crossDomain = false // 是否跨域
|
||||
var control = new AbortController()
|
||||
var noBody = NOBODY_METHODS.includes(options.method)
|
||||
|
||||
/* ------------------------ 1»» 处理超时 ---------------------- */
|
||||
// 如果有传入signal, 则删除timeout配置
|
||||
if (options.signal) {
|
||||
delete options.timeout
|
||||
} else {
|
||||
options.signal = control.signal
|
||||
}
|
||||
/* ------------------------ 1»» 处理signal ---------------------- */
|
||||
options.signal.onabort = _ => {
|
||||
this.cancel = true
|
||||
this.xhr.abort()
|
||||
|
@ -138,19 +117,25 @@ class _Request {
|
|||
params = Format.parseForm(options.body)
|
||||
hasAttach = params.constructor === FormData
|
||||
|
||||
if (hasAttach) {
|
||||
delete options.headers['content-type']
|
||||
}
|
||||
// 如果是一个 FormData对象
|
||||
// 则直接改为POST
|
||||
// 如果是一个 FormData对象,且为不允许携带body的方法,则直接改为POST
|
||||
} else if (options.body.constructor === FormData) {
|
||||
hasAttach = true
|
||||
options.method = 'POST'
|
||||
if (noBody) {
|
||||
options.method = 'POST'
|
||||
}
|
||||
params = options.body
|
||||
delete options.headers['content-type']
|
||||
} 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
|
||||
|
@ -158,6 +143,9 @@ class _Request {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (hasAttach) {
|
||||
delete options.headers['content-type']
|
||||
}
|
||||
|
||||
/* -------------------------- 3»» 处理跨域 --------------------- */
|
||||
try {
|
||||
|
@ -176,21 +164,172 @@ class _Request {
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------- 4»» 根据method类型, 处理g表单数据 ---------------- */
|
||||
/* ------------- 4»» 根据method类型, 处理表单数据 ---------------- */
|
||||
|
||||
// 拼接到url上
|
||||
if (noBody) {
|
||||
params = Format.param(params)
|
||||
if (params) {
|
||||
options.url += (~options.url.indexOf('?') ? '&' : '?') + params
|
||||
}
|
||||
if (options.cache === 'no-store') {
|
||||
options.url +=
|
||||
(~options.url.indexOf('?') ? '&' : '?') + '_t_=' + Date.now()
|
||||
}
|
||||
} else {
|
||||
if (!hasAttach) {
|
||||
if (~options.headers['content-type'].indexOf('json')) {
|
||||
params = JSON.stringify(params)
|
||||
} else {
|
||||
params = Format.param(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------- 5»» 设置响应的数据类型 ---------------- */
|
||||
// 统一使用blob, 再转为其他的
|
||||
this.xhr.responseType = 'blob'
|
||||
|
||||
/* ----------------- 6»» 构造请求 ------------------- */
|
||||
// 6.1
|
||||
this.xhr.onreadystatechange = ev => {
|
||||
if (options.timeout > 0) {
|
||||
options['time' + this.xhr.readyState] = ev.timeStamp
|
||||
if (this.xhr.readyState === 4) {
|
||||
options.isTimeout = options.time4 - options.time1 > options.timeout
|
||||
}
|
||||
}
|
||||
|
||||
if (this.xhr.readyState !== 4) {
|
||||
return
|
||||
}
|
||||
|
||||
this.__dispatch__(options.isTimeout)
|
||||
}
|
||||
|
||||
// 6.2»» 初始化xhr
|
||||
this.xhr.open(options.method, options.url)
|
||||
|
||||
// 6.3»» 设置头信息
|
||||
for (let k in options.headers) {
|
||||
this.xhr.setRequestHeader(k, options.headers[k])
|
||||
}
|
||||
|
||||
// 6.4»» 发起网络请求
|
||||
this.xhr.send(params)
|
||||
|
||||
// 6.5»» 超时处理
|
||||
if (options.timeout && options.timeout > 0) {
|
||||
this.xhr.timeout = options.timeout
|
||||
}
|
||||
}
|
||||
|
||||
__type__(type) {
|
||||
this.options.headers['content-type'] = FORM_TYPES[type]
|
||||
}
|
||||
|
||||
__dispatch__(isTimeout) {
|
||||
let result = {
|
||||
status: 200,
|
||||
statusText: 'ok',
|
||||
body: '',
|
||||
headers: Object.create(null)
|
||||
}
|
||||
|
||||
// 主动取消
|
||||
if (this.cancel) {
|
||||
return this.__cancel__()
|
||||
}
|
||||
|
||||
// 超时
|
||||
if (isTimeout) {
|
||||
return this.__timeout__()
|
||||
}
|
||||
|
||||
// 是否请求成功(resful规范)
|
||||
let isSucc = this.xhr.status >= 200 && this.xhr.status < 400
|
||||
let headers = this.xhr.getAllResponseHeaders().split('\n') || []
|
||||
|
||||
//处理返回的 Header, 拿到content-type
|
||||
for (let it of headers) {
|
||||
it = it.trim()
|
||||
if (it) {
|
||||
it = it.split(':')
|
||||
let k = it.shift().toLowerCase()
|
||||
it = it.join(':').trim()
|
||||
result.headers[k] = it
|
||||
}
|
||||
}
|
||||
|
||||
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.body = this.xhr.response
|
||||
|
||||
this.__success__(isSucc, result)
|
||||
}
|
||||
|
||||
__success__(isSucc, result) {
|
||||
var response = new _Response(
|
||||
result.status,
|
||||
result.statusText,
|
||||
result.body,
|
||||
result.headers
|
||||
)
|
||||
|
||||
if (isSucc) {
|
||||
this.defer.resolve(response)
|
||||
} else {
|
||||
this.defer.reject(response)
|
||||
}
|
||||
delete this.xhr
|
||||
delete this.options
|
||||
delete this.defer
|
||||
}
|
||||
|
||||
__cancel__(result) {
|
||||
var response = new _Response(0, ERRORS[10100], Object.create(null))
|
||||
|
||||
this.defer.reject(response)
|
||||
|
||||
delete this.xhr
|
||||
delete this.options
|
||||
delete this.defer
|
||||
}
|
||||
|
||||
__timeout__(result) {
|
||||
var response = new _Response(504, ERRORS[10504], Object.create(null))
|
||||
|
||||
this.defer.reject(response)
|
||||
|
||||
delete this.xhr
|
||||
delete this.options
|
||||
delete this.defer
|
||||
}
|
||||
}
|
||||
|
||||
class _Response {
|
||||
constructor(status = 200, data = null, headers = {}) {
|
||||
constructor(status = 200, statusText = 'OK', data = null, headers = {}) {
|
||||
this.status = status
|
||||
this.statusText = 'OK'
|
||||
this.ok = true
|
||||
this.statusText = statusText
|
||||
this.ok = status >= 200 && status < 400
|
||||
this.headers = headers
|
||||
this.__R__ = data
|
||||
|
||||
Object.defineProperty(this, '__R__', {
|
||||
value: data,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
})
|
||||
}
|
||||
|
||||
text() {
|
||||
|
@ -212,8 +351,17 @@ class _Response {
|
|||
}
|
||||
}
|
||||
|
||||
function _fetch(url, param) {
|
||||
return new _Request(url, param)
|
||||
const _fetch = function(url, options) {
|
||||
return new _Request(url, options, {
|
||||
BASE_URL: _fetch.BASE_URL,
|
||||
__INIT__: _fetch.__INIT__ || Object.create(null)
|
||||
})
|
||||
}
|
||||
|
||||
_fetch.create = function(BASE_URL, __INIT__ = Object.create(null)) {
|
||||
return function(url, options) {
|
||||
return new _Request(url, options, { BASE_URL, __INIT__ })
|
||||
}
|
||||
}
|
||||
|
||||
export default _fetch
|
||||
|
|
443
src/index222.es7
443
src/index222.es7
|
@ -1,443 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* @authors yutent (yutent.io@gmail.com)
|
||||
* @date 2018-03-25 23:59:13
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
import Format from './lib/format.js'
|
||||
|
||||
// 本地协议/头 判断正则
|
||||
// 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.protocol)
|
||||
// } 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(/#.*$/, '')
|
||||
|
||||
if (fetch.BASE_URL) {
|
||||
if (!/^([a-z]+:|\/\/)/.test(url)) {
|
||||
url = fetch.BASE_URL + url
|
||||
}
|
||||
}
|
||||
|
||||
method = method.toUpperCase()
|
||||
|
||||
this.xhr = new XMLHttpRequest()
|
||||
this.defer = Promise.defer()
|
||||
this.opt = {
|
||||
url,
|
||||
method,
|
||||
headers: {},
|
||||
data: {},
|
||||
dataType: 'blob',
|
||||
withCredentials: false // 跨域选项,是否验证凭证
|
||||
}
|
||||
|
||||
// 取消网络请求
|
||||
this.defer.promise.abort = () => {
|
||||
this.cancel = true
|
||||
this.xhr.abort()
|
||||
}
|
||||
this.__next__(Object.assign({}, fetch.__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)
|
||||
|
||||
if (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
|
||||
}
|
||||
}
|
||||
|
||||
function _fetch(url, method = 'GET', param = {}) {
|
||||
if (typeof method === 'object') {
|
||||
param = method
|
||||
method = 'GET'
|
||||
}
|
||||
return new _Request(url, method, param)
|
||||
}
|
||||
|
||||
_fetch.get = function(url, param = {}) {
|
||||
return new _Request(url, 'GET', param)
|
||||
}
|
||||
_fetch.post = function(url, param = {}) {
|
||||
return new _Request(url, 'POST', param)
|
||||
}
|
||||
_fetch.upload = function(url, param = {}) {
|
||||
param.formType = 'form-data'
|
||||
return this.post(url, param)
|
||||
}
|
||||
_fetch.download = function(url, param = {}) {
|
||||
param.dataType = 'blob'
|
||||
return this.get(url, param)
|
||||
}
|
||||
|
||||
_fetch.version = '2.0.0-normal'
|
||||
_fetch.init = function(param = {}) {
|
||||
this.__INIT__ = param
|
||||
}
|
||||
|
||||
export default _fetch
|
|
@ -5,46 +5,9 @@
|
|||
*
|
||||
*/
|
||||
|
||||
const toS = Object.prototype.toString
|
||||
const doc = window.document
|
||||
const encode = encodeURIComponent
|
||||
const decode = decodeURIComponent
|
||||
const svgTags =
|
||||
'circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use'
|
||||
|
||||
const TagHooks = 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
|
||||
|
||||
// 处理svg
|
||||
svgTags.split(',').forEach(m => {
|
||||
this[m] = this.g
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
rhtml: /<|&#?\w+;/
|
||||
}
|
||||
export const toS = Object.prototype.toString
|
||||
export const encode = encodeURIComponent
|
||||
export const decode = decodeURIComponent
|
||||
|
||||
/**
|
||||
* 表单序列化
|
||||
|
@ -72,75 +35,7 @@ function serialize(p, obj, query) {
|
|||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
parseJS(code) {
|
||||
code = (code + '').trim()
|
||||
if (code) {
|
||||
if (code.indexOf('use strict') === 1) {
|
||||
let script = doc.createElement('script')
|
||||
script.text = code
|
||||
doc.head.appendChild(script).parentNode.removeChild(script)
|
||||
} else {
|
||||
eval(code)
|
||||
}
|
||||
}
|
||||
},
|
||||
parseXML(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(html) {
|
||||
let fragment = doc.createDocumentFragment().cloneNode(false)
|
||||
|
||||
if (typeof html !== 'string') {
|
||||
return fragment
|
||||
}
|
||||
|
||||
if (!Helper.rhtml.test(html)) {
|
||||
fragment.appendChild(document.createTextNode(html))
|
||||
return fragment
|
||||
}
|
||||
|
||||
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
|
||||
let script = wrap.getElementsByTagName('script')
|
||||
if (script.length) {
|
||||
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)
|
||||
})
|
||||
tmp.text = el.text
|
||||
el.parentNode.replaceChild(tmp, el)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((firstChild = wrap.firstChild)) {
|
||||
fragment.appendChild(firstChild)
|
||||
}
|
||||
|
||||
return fragment
|
||||
},
|
||||
export const Format = {
|
||||
parseForm(form) {
|
||||
let data = {}
|
||||
let hasAttach = false
|
||||
|
|
206
src/next.es7
206
src/next.es7
|
@ -4,4 +4,208 @@
|
|||
* @date 2020/07/31 18:59:47
|
||||
*/
|
||||
|
||||
import Format from './lib/format.es7'
|
||||
import { Format, toS } from './lib/format.js'
|
||||
|
||||
const noop = function(e, res) {
|
||||
this.defer.resolve(res)
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
class _Request {
|
||||
constructor(url = '', options = {}, { BASE_URL, __INIT__ }) {
|
||||
if (!url) {
|
||||
throw new Error('Argument url is required')
|
||||
}
|
||||
|
||||
// url规范化
|
||||
url = url.replace(/#.*$/, '')
|
||||
|
||||
if (BASE_URL) {
|
||||
if (!/^([a-z]+:|\/\/)/.test(url)) {
|
||||
url = BASE_URL + url
|
||||
}
|
||||
}
|
||||
|
||||
options.method = (options.method || 'get').toUpperCase()
|
||||
|
||||
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 (__INIT__.headers && options.headers) {
|
||||
Object.assign(__INIT__.headers, options.headers)
|
||||
}
|
||||
|
||||
Object.assign(this.options, __INIT__, options, { url })
|
||||
|
||||
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
|
||||
} 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
|
||||
}
|
||||
if (options.cache === 'no-store') {
|
||||
options.url +=
|
||||
(~options.url.indexOf('?') ? '&' : '?') + '_t_=' + Date.now()
|
||||
}
|
||||
} 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 window
|
||||
.fetch(url, options)
|
||||
.then(r => {
|
||||
clearTimeout(this.timer)
|
||||
var isSucc = r.status >= 200 && r.status < 400
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
const _fetch = function(url, options) {
|
||||
return new _Request(url, options, {
|
||||
BASE_URL: _fetch.BASE_URL,
|
||||
__INIT__: _fetch.__INIT__ || Object.create(null)
|
||||
})
|
||||
}
|
||||
|
||||
_fetch.create = function(BASE_URL, __INIT__ = Object.create(null)) {
|
||||
return function(url, options) {
|
||||
return new _Request(url, options, { BASE_URL, __INIT__ })
|
||||
}
|
||||
}
|
||||
|
||||
export default _fetch
|
||||
|
|
Loading…
Reference in New Issue