Last active
October 10, 2019 03:03
-
-
Save superlbr/e3b24c74c8df2e5ca17a05f13ca7ee7f to your computer and use it in GitHub Desktop.
request using axios in miniprogram
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
/** copy axios.min.js */ | |
import axios from 'axios' | |
import Tips from './Tips' | |
const host = process.env.NODE_ENV === 'production' ? 'https://xxx.com' : 'http://localhost:3003' | |
axios.defaults.baseURL = `${host}/api` | |
axios.defaults.timeout = 10000 | |
/** follow by wepy-axios start */ | |
const supportedMethods = ['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'] | |
const supportedResponseTypes = ['json', 'text', 'file'] | |
const unSupportOptions = ['timeout', 'xsrfCookieName', 'xsrfHeaderName', 'maxContentLength'] | |
function createError(message, config, code, request, response) { | |
var error = new Error(message) | |
error.config = config | |
if (code) error.code = code | |
error.request = request | |
error.response = response | |
return error | |
} | |
function settle(resolve, reject, response) { | |
var validateStatus = response.config.validateStatus | |
// Note: status is not exposed by XDomainRequest | |
if (!response.status || !validateStatus || validateStatus(response.status)) { | |
resolve(response) | |
} else { | |
reject(createError( | |
'网络访问失败 ' + response.status, | |
response.config, | |
null, | |
response.request, | |
response | |
)) | |
} | |
} | |
/** | |
* 将 headers 对象中的 key 都转为标准格式 | |
* | |
* @param {object} headers 要转换的 headers 对象 | |
*/ | |
function normalizeHeaders(headers) { | |
if (headers == null) return {} | |
const result = {} | |
Object.keys(headers).forEach(key => { | |
if (headers[key] == null) return | |
const keyParts = key.split('-').map(part => { | |
return part[0].toUpperCase() + part.slice(1).toLowerCase() | |
}) | |
result[keyParts.join('-')] = headers[key] | |
}) | |
return result | |
} | |
/** | |
* 将 headers 对象中的 key 都转为小写 | |
* | |
* @param {object} headers 要转换的 headers 对象 | |
*/ | |
function lowerCaseHeaders(headers) { | |
if (headers == null) return {} | |
const result = {} | |
Object.keys(headers).forEach(key => { | |
result[key.toLowerCase()] = headers[key] | |
}) | |
return result | |
} | |
/** | |
* URL 编码 | |
* | |
* @param {string} val 要编码的字符串 | |
*/ | |
function encode(val) { | |
return encodeURIComponent(val) | |
.replace(/%40/gi, '@') | |
.replace(/%3A/gi, ':') | |
.replace(/%24/g, '$') | |
.replace(/%2C/gi, ',') | |
.replace(/%20/g, '+') | |
.replace(/%5B/gi, '[') | |
.replace(/%5D/gi, ']') | |
} | |
/** | |
* 拼接 URL 与参数 | |
* | |
* @param {string} url 原 URL | |
* @param {object} params 要拼接的参数 | |
* @param {Function} paramsSerializer 参数序列化方法 | |
*/ | |
function buildURL(url, params, paramsSerializer) { | |
if (!params) return url | |
var serializedParams | |
if (paramsSerializer) { | |
serializedParams = paramsSerializer(params) | |
} else { | |
var parts = [] | |
Object.keys(params).forEach(key => { | |
var val = params[key] | |
if (val == null) return | |
if (Array.isArray(val)) { | |
key = key + '[]' | |
} else { | |
val = [val] | |
} | |
Object.keys(val).forEach(valKey => { | |
var v = val[valKey] | |
if (Object.prototype.toString.call(v) === '[object Date]') { | |
v = v.toISOString() | |
} else if (v != null && typeof v === 'object') { | |
v = JSON.stringify(v) | |
} | |
parts.push(encode(key) + '=' + encode(v)) | |
}) | |
}) | |
serializedParams = parts.join('&') | |
} | |
if (serializedParams) { | |
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams | |
} | |
return url | |
} | |
// 不使用默认的 transformResponse | |
axios.defaults['transformResponse'] = [] | |
// 使用自定义的 transformRequest | |
axios.defaults['transformRequest'] = [(data, headers) => { | |
const contentTypeKey = Object.keys(headers).find(key => /content-type/i.test(key)) | |
if (contentTypeKey == null && data != null && typeof data === 'object') { | |
headers['Content-Type'] = 'application/json; charset=utf-8' | |
} | |
return data | |
}] | |
// 删除无法使用的 axios 选项 | |
unSupportOptions.forEach(item => { | |
delete axios.defaults[item] | |
}) | |
// 请求队列 | |
const queue = [] | |
let pendingRequests = 0 // 未完成的请求数 | |
const MAX_PENDING_REQUESTS = 5 // 最多同时发送的请求数 | |
// 检查是否可以发送下一个请求. 如果可以,则发送 | |
function checkQueue() { | |
while (pendingRequests < MAX_PENDING_REQUESTS) { | |
const item = queue.shift() | |
if (!item) break | |
pendingRequests++ | |
new Promise(item.promiseFunc).then(result => { | |
pendingRequests-- | |
item.resolve(result) | |
checkQueue() | |
}).catch(err => { | |
pendingRequests-- | |
item.reject(err) | |
checkQueue() | |
}) | |
} | |
} | |
/** | |
* 将一个请求加入队列中. 如果当前没有正在排队的请求则立即发送 | |
* | |
* @param {Function} promiseFunc 要入队的请求 | |
*/ | |
function enqueue(promiseFunc) { | |
return new Promise((resolve, reject) => { | |
queue.push({ | |
resolve, | |
reject, | |
promiseFunc | |
}) | |
checkQueue() | |
}) | |
} | |
// 请求封装 | |
axios.defaults.adapter = function (config) { | |
return enqueue((resolve, reject) => { | |
/** 发送普通请求或是上传、下载请求 */ | |
let requestType = 'request' | |
/** 请求所使用的参数 */ | |
let request = { | |
url: buildURL(config.url, config.params, config.paramsSerializer), | |
header: normalizeHeaders(config.headers) | |
} | |
// 必须在 URL 中指定是 http 还是 https 协议 | |
if (!/https?:\/\//.test(request.url)) { | |
return reject(createError('Request protocol must be http or https', config)) | |
} | |
// 处理调用上传文件接口的情况 | |
if (config.data && config.data.$upload) { | |
const { | |
filePath, | |
name | |
} = config.data.$upload | |
if (typeof filePath === 'string' && typeof name === 'string') { | |
if (config.method !== 'post') { | |
return reject(createError('HTTP methods must be POST when uploading file', config)) | |
} | |
requestType = 'uploadFile' | |
config.responseType = 'file' | |
request.filePath = filePath | |
request.name = name | |
request.formData = config.data | |
delete config.data.$upload | |
} else { | |
return reject(createError('Invalid upload paramaters', config)) | |
} | |
} | |
// 处理响应类型和下载接口类型 | |
if (config.responseType && supportedResponseTypes.indexOf(config.responseType) === -1) { | |
// 只接受 json 或 text 的返回类型 | |
return reject(createError('Unsupported responseType', config)) | |
} else if (requestType === 'request') { | |
// 确认是否调用下载文件接口 | |
if (config.responseType === 'file') { | |
if (config.method !== 'get') { | |
reject(createError('HTTP method must be GET when downloading file', config)) | |
} else { | |
requestType = 'downloadFile' | |
} | |
} else { | |
// 普通请求必须使用 https | |
if (process.env.NODE_ENV === 'production' && config.url.slice(0, 5).toLowerCase() !== 'https') { | |
return reject(createError('Requesting an http URL is not allowed', config)) | |
} | |
// 检查小程序是否支持所使用的 method | |
config.method = config.method.toUpperCase() | |
if (supportedMethods.indexOf(config.method) === -1) { | |
return reject(createError('Unsupported request method', config)) | |
} | |
request.data = config.data | |
request.method = config.method | |
// 增加 Content-Type | |
if (!request.header['Content-Type'] && config.data && typeof config.data === 'object') { | |
request.header['Content-Type'] = 'application/json; charset=utf-8' | |
} | |
} | |
} | |
// 加入回调函数 | |
request.success = response => { | |
// 按照 axios 的返回格式构造返回值 | |
settle(resolve, reject, { | |
data: response.data || response.tempFilePath, | |
status: response.statusCode, | |
headers: lowerCaseHeaders(response.header), | |
config, | |
request | |
}) | |
} | |
request.fail = response => reject(createError(response.errMsg, config)) | |
// 发送请求 | |
wx[requestType](request) | |
}) | |
} | |
/** follow by wepy-axios end */ | |
// axios 内置的中断ajax的方法 | |
const cancelQueue = [] | |
const CancelToken = axios.CancelToken | |
// 拼接请求的url和方法,同样的url+方法可以视为相同的请求 | |
const token = config => `${config.url}_${config.method}` | |
// 中断重复的请求,并从队列中移除 | |
const removeQueue = config => { | |
let len = cancelQueue.length | |
while (len--) { | |
const task = cancelQueue[len] | |
if (task.token === token(config)) { | |
task.cancel() | |
cancelQueue.splice(len, 1) | |
} | |
} | |
} | |
const instance = axios.create() | |
// 添加请求拦截器 | |
instance.interceptors.request.use(config => { | |
removeQueue(config) // 中断之前的同名请求 | |
// 添加cancelToken | |
config.cancelToken = new CancelToken(c => { | |
cancelQueue.push({ | |
token: token(config), | |
cancel: c | |
}) | |
}) | |
return config | |
}, error => { | |
return Promise.reject(error) | |
}) | |
// 添加响应拦截器 | |
instance.interceptors.response.use(response => { | |
// 在请求完成后,自动移出队列 | |
const { | |
config, | |
status, | |
data | |
} = response | |
removeQueue(config) | |
if (!status) return Tips.error('服务器未知错误') | |
if ([404, 504].includes(status)) return Tips.error('服务器失去响应') | |
if (status === 403) return Tips.error('账户权限不足') | |
if (data.success === false) { | |
let message = data.message | |
if (message === 'BusinessError: ORDERPAID') { | |
message = '订单已支付' | |
} | |
return Tips.error(message) | |
} | |
return data.data | |
}, err => { | |
return Promise.resolve(err) | |
}) | |
/** | |
* 封装后的ajax get方法 | |
* | |
* @param {string} url 请求路径 | |
* @param {object} params 请求参数 | |
* @param {object} config 用户自定义设置 | |
* @returns | |
*/ | |
function get(url, params = {}, config = {}) { | |
const { key } = wx.getStorageSync('authority') | |
if (key) params.key = key | |
return instance.get(url, { params }, config) | |
} | |
/** | |
* 封装后的ajax post方法 | |
* | |
* @param {string} url 请求路径 | |
* @param {object} data 请求参数 | |
* @param {object} config 用户自定义设置 | |
* @returns | |
*/ | |
function post(url, data = {}, config = {}) { | |
const { key } = wx.getStorageSync('authority') | |
if (key) data.key = key | |
return instance.post(url, data, config) | |
} | |
export default { | |
get, | |
post | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment