This commit is contained in:
2025-06-16 10:09:19 +08:00
commit 7a066b3026
428 changed files with 50385 additions and 0 deletions

103
uni_modules/uni-ajax/js_sdk/index.d.ts vendored Normal file
View File

@ -0,0 +1,103 @@
export type AnyObject = { [x: string]: any }
export type Data = string | AnyObject | ArrayBuffer
export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'HEAD' | 'OPTIONS' | 'TRACE'
export type DataType = 'json' | 'text' | 'html'
export type ResponseType = 'text' | 'arraybuffer'
export interface RequestTask {
abort: () => void
onHeadersReceived?: (listener: (header: any) => void) => void
offHeadersReceived?: (listener: (header: any) => void) => void
}
export interface FetcherInstance<T = any> {
resolve: (value: T) => void
reject: (reason?: any) => void
source: () => Promise<T>
abort: () => Promise<void>
}
export interface FetcherConstructor {
new <T = RequestTask>(): FetcherInstance<T>
}
export interface CustomConfig {}
export interface AjaxRequestConfig extends CustomConfig {
baseURL?: string
url?: string
data?: Data
query?: AnyObject
params?: AnyObject
header?: any
method?: Method
timeout?: number
dataType?: DataType
responseType?: ResponseType
sslVerify?: boolean
withCredentials?: boolean
firstIpv4?: boolean
fetcher?: FetcherInstance
validateStatus?: ((statusCode?: number) => boolean) | null
adapter?: (config: AjaxRequestConfig) => Promise<any>
}
export type AjaxConfigType =
| AjaxRequestConfig
| (() => AjaxRequestConfig)
| (() => Promise<AjaxRequestConfig>)
| void
export interface AjaxResponse<T = any> {
data: T
statusCode: number
header: any
config: AjaxRequestConfig
errMsg: string
cookies: string[]
}
export interface AjaxInterceptorManager<V> {
use<T = V>(onFulfilled?: (value: V) => T | Promise<T>, onRejected?: (error: any) => any): number
eject(id: number): void
}
export interface CustomResponse<T = any> {}
export type AjaxResult<T> = keyof CustomResponse extends never ? AjaxResponse<T> : CustomResponse<T>
export interface AjaxInvoke {
<T = any, R = AjaxResult<T>>(config?: AjaxRequestConfig): Promise<R>
<T = any, R = AjaxResult<T>>(url?: string, data?: Data, config?: AjaxRequestConfig): Promise<R>
}
export interface AjaxInstance<T extends AjaxConfigType> extends AjaxInvoke {
get: AjaxInvoke
post: AjaxInvoke
put: AjaxInvoke
delete: AjaxInvoke
connect: AjaxInvoke
head: AjaxInvoke
options: AjaxInvoke
trace: AjaxInvoke
getURL(config?: AjaxConfigType): Promise<string>
readonly defaults: AjaxRequestConfig
readonly config: T
interceptors: {
request: AjaxInterceptorManager<AjaxRequestConfig>
response: AjaxInterceptorManager<AjaxResponse>
}
}
export interface AjaxStatic extends AjaxInstance<void> {
create<T extends AjaxConfigType = void>(config?: T): AjaxInstance<T>
Fetcher: FetcherConstructor
}
declare const Ajax: AjaxStatic
declare const Fetcher: FetcherConstructor
export { Fetcher }
export default Ajax

View File

@ -0,0 +1,14 @@
import Ajax from './lib/core/Ajax'
import Fetcher from './lib/adapters/Fetcher'
const ajax = Ajax()
ajax.create = function create(instanceConfig) {
return Ajax(instanceConfig)
}
ajax.Fetcher = Fetcher
export { Fetcher }
export default ajax

View File

@ -0,0 +1,22 @@
const PROMISE = Symbol('$$promise')
export default class Fetcher {
get [Symbol.toStringTag]() {
return '[object Fetcher]'
}
constructor() {
this[PROMISE] = new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
}
async source() {
return this[PROMISE]
}
async abort() {
;(await this.source())?.abort()
}
}

View File

@ -0,0 +1,16 @@
export default function adapter(config) {
return new Promise((resolve, reject) => {
const requestTask = uni.request({
...config,
complete: result => {
// 根据状态码判断要执行的触发的状态
const response = { config, ...result }
!config.validateStatus || config.validateStatus(result.statusCode)
? resolve(response)
: reject(response)
}
})
config.fetcher?.resolve(requestTask)
})
}

View File

@ -0,0 +1,72 @@
import InterceptorManager from './InterceptorManager'
import buildURL from '../helpers/buildURL'
import mergeConfig from '../helpers/mergeConfig'
import dispatchRequest from './dispatchRequest'
import { detachCancel, dispatchCancel, interceptCancel } from './handleCancel'
import defaults, { METHOD } from '../defaults'
import { forEach, merge } from '../utils'
export default function Ajax(defaultConfig) {
// 挂载当前实例配置
ajax.config = Object.freeze(
typeof defaultConfig === 'object' ? merge(defaultConfig) : defaultConfig
)
// 挂载全局默认配置引用
ajax.defaults = defaults
// 挂载拦截器
ajax.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
// 挂载获取实例请求地址方法
ajax.getURL = async function getURL(config) {
const combineConfig = await mergeConfig(defaults, defaultConfig, config)
return buildURL(combineConfig).replace(/^\?/, '')
}
// 挂载对应的 method 方法
forEach(METHOD, method => {
ajax[method] = function methodAjax(url, data, config) {
if (typeof url === 'string') return ajax(url, data, { ...config, method })
return ajax({ ...url, method })
}
})
function ajax(url, data, config) {
const newConfig = typeof url === 'string' ? { ...config, url, data } : { ...url }
// 声明 Promise 链
const chain = [dispatchRequest, dispatchCancel]
// 将请求拦截遍历添加到链前面
ajax.interceptors.request.forEach(
({ fulfilled, rejected }) => chain.unshift(fulfilled, rejected),
true
)
// 将响应拦截遍历添加到链后面
ajax.interceptors.response.forEach(
({ fulfilled, rejected }) => chain.push(fulfilled, interceptCancel(rejected)),
false
)
// 先执行获取 config 请求配置
chain.unshift(config => mergeConfig(defaults, defaultConfig, config), undefined)
// 处理发起请求前的错误数据
chain.push(undefined, detachCancel)
// 创建请求Promise,后面遍历链将链上方法传递到then回调
let request = Promise.resolve(newConfig)
while (chain.length) {
request = request.then(chain.shift(), chain.shift())
}
return request
}
return ajax
}

View File

@ -0,0 +1,32 @@
/**
* 拦截器类
*/
export default class InterceptorManager {
handlers = []
use(fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected
})
return this.handlers.length - 1
}
eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null
}
}
forEach(fn, reverse = false) {
if (reverse) {
for (let i = this.handlers.length - 1; i >= 0; i--) {
this.handlers[i] !== null && fn(this.handlers[i])
}
} else {
for (let i = 0, l = this.handlers.length; i < l; i++) {
this.handlers[i] !== null && fn(this.handlers[i])
}
}
}
}

View File

@ -0,0 +1,29 @@
import buildURL from '../helpers/buildURL'
import isCallback from '../helpers/isCallback'
import { forEach, isPlainObject, merge } from '../utils'
import { HEADER } from '../defaults'
/** 派发请求方法 */
export default function dispatchRequest(config) {
// 拼接 url
config.url = buildURL(config)
// 请求方法转大写
config.method = (config.method || 'get').toUpperCase()
// 调整 header 优先级
config.header = merge(
config.header.common,
config.header[config.method.toLowerCase()],
config.header
)
// 清除多余的请求头
forEach(HEADER, h => isPlainObject(config.header[h]) && delete config.header[h])
// 清除回调函数
forEach(config, (val, key) => isCallback(key) && delete config[key])
// 执行请求方法
return config.adapter(config)
}

View File

@ -0,0 +1,35 @@
const CANCEL = Symbol('$$cancel')
function hasCancel(target) {
return target === null || target === undefined
? false
: Object.prototype.hasOwnProperty.call(target, CANCEL)
}
/**
* 派发请求拒绝方法,处理发起请求前错误,取消执行请求,并防止进入响应拦截器
* @param {*} reason 错误原因
* @returns {Promise} 封装了 CANCEL 的失败对象
*/
export function dispatchCancel(reason) {
return Promise.reject({ [CANCEL]: reason })
}
/**
* 拦截失败对象
* @param {Function} rejected 响应错误拦截器
*/
export function interceptCancel(rejected) {
// 判断发起请求前是否发生错误,如果发生错误则不执行后面的响应错误拦截器
return (
rejected && (response => (hasCancel(response) ? Promise.reject(response) : rejected(response)))
)
}
/**
* 分离失败对象
* @param {*} response 封装了 CANCEL 的失败对象
*/
export function detachCancel(error) {
return Promise.reject(hasCancel(error) ? error[CANCEL] : error)
}

View File

@ -0,0 +1,16 @@
import adapter from './adapters/http'
import { forEach } from './utils'
export const METHOD = ['get', 'post', 'put', 'delete', 'connect', 'head', 'options', 'trace']
export const HEADER = ['common', ...METHOD]
const defaults = {
adapter,
header: {},
method: 'GET',
validateStatus: statusCode => statusCode >= 200 && statusCode < 300
}
forEach(HEADER, h => (defaults.header[h] = {}))
export default defaults

View File

@ -0,0 +1,83 @@
import { forEach, isArray } from '../utils'
/**
* 根据 baseURL 和 url 拼接
* @param {string} baseURL 请求根地址
* @param {string} relativeURL 请求参数地址
* @returns {string} 拼接后的地址
*/
function combineURL(baseURL = '', relativeURL = '') {
// 判断是否 http:// 或 https:// 开头
if (/^https?:\/\//.test(relativeURL)) return relativeURL
// 去除 baseURL 结尾斜杠,去除 relativeURL 开头斜杠,再判断拼接
return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL
}
function encode(val) {
return encodeURIComponent(val)
.replace(/%3A/gi, ':')
.replace(/%24/g, '$')
.replace(/%2C/gi, ',')
.replace(/%20/g, '+')
.replace(/%5B/gi, '[')
.replace(/%5D/gi, ']')
}
function querystring(url, params) {
let query
const parts = []
forEach(params, (val, key) => {
if (val === null || typeof val === 'undefined') return
if (isArray(val)) key = key + '[]'
else val = [val]
forEach(val, v => {
if (v !== null && typeof v === 'object') {
v = JSON.stringify(v)
}
parts.push(encode(key) + '=' + encode(v))
})
})
query = parts.join('&')
if (query) {
const hashmarkIndex = url.indexOf('#')
hashmarkIndex !== -1 && (url = url.slice(0, hashmarkIndex))
url += (url.indexOf('?') === -1 ? '?' : '&') + query
}
return url
}
/**
* 根据 baseURL、url、params query 编译请求URL
* @returns {string} 处理后的地址
*/
export default function buildURL({ baseURL, url: relativeURL, params, query } = {}) {
let url = combineURL(baseURL, relativeURL)
if (!params && !query) {
return url
}
if (params) {
if (/\/:/.test(url)) {
// 是否是 params 参数地址,并对应设置
forEach(params, (val, key) => {
url = url.replace(`/:${key}`, `/${encode(String(val))}`)
})
} else if (!query) {
// 兼容旧的 params 属性设置 query
url = querystring(url, params)
}
}
// 设置 query 参数
if (query) {
url = querystring(url, query)
}
return url
}

View File

@ -0,0 +1,8 @@
/**
* 判断参数是否含有回调参数 success / fail / complete 之一
* @param {string} field 参数的 Key 值字符串
* @returns {boolean} 返回判断值
*/
export default function isCallback(field) {
return ['success', 'fail', 'complete'].includes(field)
}

View File

@ -0,0 +1,41 @@
import { assign, forEach } from '../utils'
/**
* 深度合并,且不合并 undefined 值
* @param {object} obj1 前对象
* @param {object} obj2 后对象
* @returns {object} 合并后的对象
*/
function merge(obj1 = {}, obj2 = {}) {
const obj = {}
const objKeys = Object.keys({ ...obj1, ...obj2 })
forEach(objKeys, prop => {
if (obj2[prop] !== undefined) {
obj[prop] = assign(obj1[prop], obj2[prop])
} else if (obj1[prop] !== undefined) {
obj[prop] = assign(undefined, obj1[prop])
}
})
return obj
}
/**
* 合并请求配置
* @param {...object|function} args 请求配置
* @returns {object} 合并后的请求配置
*/
export default async function mergeConfig(...args) {
let config = {}
for (let i = 0, l = args.length; i < l; i++) {
const current = typeof args[i] === 'function' ? await args[i]() : args[i]
config = merge(config, current)
}
config.method = config.method.toUpperCase()
return config
}

View File

@ -0,0 +1,77 @@
/**
* 获取值的原始类型字符串,例如 [object Object]
*/
const _toString = Object.prototype.toString
/**
* 判断是否为数组
* @param {*} val 要判断的值
* @returns {boolean} 返回判断结果
*/
export function isArray(val) {
return _toString.call(val) === '[object Array]'
}
/**
* 判断是否为普通对象
* @param {*} val 要判断的值
* @returns {boolean} 返回判断结果
*/
export function isPlainObject(val) {
return _toString.call(val) === '[object Object]'
}
/**
* 遍历
* @param {object|array} obj 要迭代的对象
* @param {function} fn 为每个项调用的回调
*/
export function forEach(obj, fn) {
if (obj === null || obj === undefined) return
if (typeof obj !== 'object') obj = [obj]
if (isArray(obj)) {
for (let i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj)
}
} else {
for (const k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
fn.call(null, obj[k], k, obj)
}
}
}
}
/**
* 对象深合并
* @param {...object} args 对象
* @returns {object} 合并后的对象
*/
export function merge(...args) {
const result = {}
for (let i = 0, l = args.length; i < l; i++) {
if (isPlainObject(args[i])) {
forEach(args[i], (val, key) => {
result[key] = assign(result[key], val)
})
}
}
return result
}
/**
* 合并分配到目标对象
* @param {*} target 目标对象
* @param {*} source 源对象
* @returns {*} 目标对象
*/
export function assign(target, source) {
if (isPlainObject(target) && isPlainObject(source)) {
return merge(target, source)
} else if (isPlainObject(source)) {
return merge({}, source)
} else if (isArray(source)) {
return source.slice()
}
return source
}