├── index.js ├── .gitignore ├── src ├── index.js ├── adapters │ ├── axios.js │ ├── index.js │ ├── quickapp.js │ ├── xhr.js │ ├── jquery.js │ └── wx.js ├── queue.js ├── canceltoken.js ├── utils.js └── http.js ├── .editorconfig ├── webpack.config.js ├── example ├── server.js └── index.html ├── .circleci └── config.yml ├── package.json ├── test ├── testaxios.js └── index.js ├── reference.md ├── comparison.md ├── .readme.md ├── README.md └── dist └── http.js /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/index.js') 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.map 4 | *.log 5 | yarn.lock 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('min-util') 2 | var HttpClient = require('./http') 3 | 4 | var instance = new HttpClient() 5 | 6 | module.exports = instance 7 | -------------------------------------------------------------------------------- /src/adapters/axios.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | var defaults = this.defaults 3 | if (defaults && defaults.axios) { 4 | // https://github.com/axios/axios 5 | return defaults.axios.request(config).then(function(response) { 6 | return response 7 | }) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/adapters/index.js: -------------------------------------------------------------------------------- 1 | var wx = require('./wx') 2 | var quickapp = require('./quickapp') 3 | var axios = require('./axios') 4 | var jquery = require('./jquery') 5 | var xhr = require('./xhr') 6 | 7 | exports.wx = wx 8 | exports.quickapp = quickapp 9 | exports.axios = axios 10 | exports.jquery = jquery 11 | exports.xhr = xhr 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: 2 | 3 | root = true 4 | 5 | # 此文件里面既有 tab 又有4空格 还有2空格,统一改为2空格 6 | 7 | [*] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [Makefile] 16 | indent_style = tab 17 | 18 | [{package.json,.travis.yml}] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /src/queue.js: -------------------------------------------------------------------------------- 1 | var _ = require('min-util') 2 | 3 | module.exports = Queue 4 | 5 | function Queue() { 6 | this.queue = [] 7 | } 8 | 9 | _.extend(Queue.prototype, { 10 | use: function() { 11 | this.queue.push(arguments) 12 | return this 13 | }, 14 | intercept: function(promise) { 15 | return _.reduce(this.queue, function(prev, middlewares) { 16 | return prev.then.apply(prev, middlewares) 17 | }, promise) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /src/canceltoken.js: -------------------------------------------------------------------------------- 1 | module.exports = Deferred 2 | 3 | function Deferred() { 4 | var me = this 5 | me.promise = new Promise(function(resolve, reject) { 6 | me.resolve = resolve 7 | me.reject = reject 8 | }) 9 | } 10 | 11 | Deferred.source = function() { 12 | var deferred = new Deferred() 13 | return { 14 | token: deferred, 15 | cancel: function(reason) { 16 | deferred.resolve(new Error(reason)) 17 | } 18 | } 19 | } 20 | 21 | Deferred.prototype.throwIfRequested = function() { 22 | // 兼容 axios 23 | } 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var path = require('path') 3 | var pkg = require('./package.json') 4 | var util = require('util') 5 | var now = new Date().toISOString() 6 | 7 | var banner = ` 8 | ${pkg.name}@${pkg.version} by ${pkg.author} 9 | ${now} 10 | `.trim() 11 | 12 | var config = { 13 | entry: { 14 | 'http': './', 15 | }, 16 | mode: 'production', 17 | // devtool: false, 18 | // mode: 'development', 19 | output: { 20 | path: path.join(__dirname, 'dist'), 21 | filename: '[name].js', 22 | libraryTarget: 'umd', 23 | library: '[name]' 24 | }, 25 | plugins: [ 26 | new webpack.BannerPlugin(banner), 27 | new webpack.DefinePlugin({ 28 | VERSION: JSON.stringify(pkg.version) 29 | }), 30 | new webpack.ProvidePlugin({ 31 | Promise: 'min-promise' 32 | }) 33 | ] 34 | } 35 | 36 | module.exports = config 37 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var bodyParser = require('body-parser') 3 | var path = require('path') 4 | var app = express() 5 | 6 | var port = 9999 7 | 8 | app 9 | .use(bodyParser.urlencoded({ extended: false })) 10 | .use(bodyParser.json()) 11 | .use((req, res, next) => { 12 | res.cookie('a', 'b') 13 | res.cookie('c', 'd') 14 | next() 15 | }) 16 | 17 | .use('/json', (req, res) => { 18 | setTimeout(() => { 19 | res.send(req.body) 20 | }, 200) 21 | }) 22 | 23 | .use('/timeout', (req, res) => { 24 | setTimeout(() => { 25 | res.send(req.body) 26 | }, 2000) 27 | }) 28 | 29 | .use('/text', (req, res) => { 30 | res.send('xxxxx') 31 | }) 32 | 33 | .use('/badjson', (req, res) => { 34 | res.set('Content-Type', 'application/json') 35 | res.send('xxxxxx') 36 | }) 37 | 38 | .use('/jsontext', (req, res) => { 39 | res.send(JSON.stringify(req.body)) 40 | }) 41 | 42 | .use('/', express.static(path.resolve(__dirname, '..'))) 43 | 44 | .listen(port, err => { 45 | console.log(`listen on http://localhost:${port}/example`) 46 | }) 47 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:7.10 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: yarn test 38 | 39 | 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chunpu/http", 3 | "version": "3.0.0", 4 | "description": "Promise Based request / fetch / http For Real Project, Support multiple platforms", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test && node test/testaxios", 8 | "build": "webpack" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/chunpu/http.git" 13 | }, 14 | "author": "chunpu", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/chunpu/http/issues" 18 | }, 19 | "homepage": "https://github.com/chunpu/http#readme", 20 | "keywords": [ 21 | "$http", 22 | "http", 23 | "request", 24 | "fetch", 25 | "client", 26 | "wxapp", 27 | "quickapp", 28 | "axios", 29 | "ajax" 30 | ], 31 | "dependencies": { 32 | "min-promise": "^5.0.0", 33 | "min-qs": "^1.4.0", 34 | "min-url": "^1.5.0", 35 | "min-util": "^3.3.1" 36 | }, 37 | "devDependencies": { 38 | "axios": "^0.18.0", 39 | "body-parser": "^1.18.3", 40 | "express": "^4.16.3", 41 | "webpack": "^4.17.2", 42 | "webpack-cli": "^3.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/adapters/quickapp.js: -------------------------------------------------------------------------------- 1 | var utils = require('../utils') 2 | 3 | module.exports = function(config) { 4 | var defaults = this.defaults 5 | if (defaults && defaults.quickapp) { 6 | // https://doc.quickapp.cn/features/system/fetch.html 7 | return new Promise(function(resolve, reject) { 8 | defaults.quickapp.fetch({ 9 | url: config.url, 10 | data: config.data, 11 | header: config.headers, 12 | method: config.method, 13 | success: function(response) { 14 | utils.clearTimer(timer) 15 | resolve({ 16 | data: response.data, 17 | status: response.code, 18 | headers: response.headers 19 | }) 20 | }, 21 | fail: function(data, code) { 22 | utils.clearTimer(timer) 23 | reject({data: data, code: code}) 24 | } 25 | }) 26 | 27 | if (config.timeout) { 28 | timer = setTimeout(function() { 29 | reject(utils.createError('timeout')) 30 | }, config.timeout) 31 | } 32 | 33 | if (config.cancelToken) { 34 | // not real cancel, wait for api 35 | config.cancelToken.promise.then(function onCancel(reason) { 36 | reject(reason) 37 | }) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/adapters/xhr.js: -------------------------------------------------------------------------------- 1 | var utils = require('../utils') 2 | var _ = require('min-util') 3 | 4 | module.exports = function(config) { 5 | // default use XMLHttpRequest 6 | return new Promise(function(resolve, reject) { 7 | var xhr = new XMLHttpRequest() 8 | xhr.onload = function(ev) { 9 | resolve({ 10 | status: xhr.status, 11 | data: xhr.responseText, 12 | headers: utils.parseHeadersFromXhr(xhr) 13 | }) 14 | } 15 | xhr.ontimeout = function(ev) { 16 | reject(utils.createError('timeout')) 17 | } 18 | xhr.onerror = function(ev) { 19 | reject(utils.createError('error')) 20 | } 21 | xhr.open(config.method, config.url, true) 22 | if (config.timeout) { 23 | xhr.timeout = config.timeout 24 | } 25 | if (config.withCredentials) { 26 | xhr.withCredentials = config.withCredentials 27 | } 28 | _.forIn(config.headers, function(value, key) { 29 | xhr.setRequestHeader(key, value) 30 | }) 31 | 32 | if (config.cancelToken) { 33 | config.cancelToken.promise.then(function onCancel(reason) { 34 | if (xhr) { 35 | xhr.abort() 36 | reject(reason) 37 | xhr = null 38 | } 39 | }) 40 | } 41 | 42 | xhr.send(config.data) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /src/adapters/jquery.js: -------------------------------------------------------------------------------- 1 | var utils = require('../utils') 2 | 3 | module.exports = function(config) { 4 | var defaults = this.defaults 5 | if (defaults && defaults.jQuery) { 6 | // http://api.jquery.com/jquery.ajax/ 7 | return new Promise(function(resolve, reject) { 8 | var xhr = defaults.jQuery.ajax({ 9 | url: config.url, 10 | data: config.data, 11 | headers: config.headers, 12 | method: config.method, 13 | timeout: config.timeout, 14 | withCredentials: config.withCredentials, 15 | success: function(data, textStatus, jqXHR) { 16 | resolve({ 17 | data: data, 18 | status: 200, 19 | headers: utils.parseHeadersFromXhr(jqXHR) 20 | }) 21 | }, 22 | error: function(jqXHR, textStatus, errorThrown) { 23 | reject(utils.createError(errorThrown, { 24 | response: jqXHR, 25 | textStatus: textStatus 26 | })) 27 | } 28 | }) 29 | 30 | if (config.cancelToken) { 31 | config.cancelToken.promise.then(function onCancel(reason) { 32 | if (xhr) { 33 | xhr.abort() 34 | reject(reason) 35 | xhr = null 36 | } 37 | }) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/testaxios.js: -------------------------------------------------------------------------------- 1 | const _ = require('min-util') 2 | const httpClient = require('../') 3 | const assert = require('assert') 4 | const axios = require('axios') 5 | 6 | function testNormal() { 7 | const http = httpClient.create({ 8 | axios: axios 9 | }) 10 | 11 | http.get('https://so.com', {}).then(({data}) => { 12 | assert.deepEqual(//i.test(data), true) 13 | }).catch(err => { 14 | assert(false) 15 | }) 16 | } 17 | 18 | function testCancel() { 19 | const http = httpClient.create({ 20 | axios: axios 21 | }) 22 | 23 | var source = http.CancelToken.source() 24 | http.get('https://so.com', { 25 | params: { 26 | should: 'cancen' 27 | }, 28 | cancelToken: source.token 29 | }).then(({data}) => { 30 | assert(false) 31 | }).catch(err => { 32 | assert.deepEqual(err.message, 'source cancel') 33 | }) 34 | setTimeout(() => { 35 | source.cancel('source cancel') 36 | }, 10) 37 | } 38 | 39 | function testTimeout() { 40 | const http = httpClient.create({ 41 | axios: axios, 42 | timeout: 10 43 | }) 44 | http.get('https://so.com', { 45 | params: { 46 | should: 'timeout' 47 | } 48 | }).then(({data}) => { 49 | assert(false) 50 | }).catch(err => { 51 | assert.deepEqual(/timeout/.test(err.message), true) 52 | }) 53 | } 54 | 55 | testNormal() 56 | testCancel() 57 | testTimeout() 58 | -------------------------------------------------------------------------------- /src/adapters/wx.js: -------------------------------------------------------------------------------- 1 | var utils = require('../utils') 2 | 3 | module.exports = function(config) { 4 | var defaults = this.defaults 5 | if (defaults && defaults.wx) { 6 | // https://developers.weixin.qq.com/miniprogram/dev/api/network-request.html#wxrequestobject 7 | var timer 8 | return new Promise(function(resolve, reject) { 9 | var task = defaults.wx.request({ 10 | url: config.url, 11 | data: config.data, 12 | header: config.headers, 13 | method: config.method, 14 | success: function(response) { 15 | utils.clearTimer(timer) 16 | resolve({ 17 | data: response.data, 18 | status: response.statusCode, 19 | headers: response.header 20 | }) 21 | }, 22 | fail: function(err) { 23 | utils.clearTimer(timer) 24 | reject(err) 25 | } 26 | }) 27 | 28 | if (config.timeout) { 29 | timer = setTimeout(function() { 30 | if (task && task.abort) { 31 | task.abort 32 | } 33 | task = null 34 | reject(utils.createError('timeout')) 35 | }, config.timeout) 36 | } 37 | 38 | if (config.cancelToken) { 39 | config.cancelToken.promise.then(function onCancel(reason) { 40 | if (task && task.abort) { 41 | task.abort() 42 | reject(reason) 43 | task = null 44 | } 45 | }) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /reference.md: -------------------------------------------------------------------------------- 1 | ## jquery 2 | 3 | default post body application/x-www-form-urlencoded 4 | 5 | auto parse response application/json 6 | 7 | 根据 dataType 处理返回结果, dataType 默认自动猜测 8 | 9 | response: data, statusText, xhr(status) 10 | 11 | error: xhr, errorMessage, error 12 | 13 | post 发送 string 自动使用 application/x-www-form-urlencoded 14 | 15 | jquery 通过 application/json 判断是否解析 json, 返回 mime = text 的话不会解析成json 16 | 17 | dataType 只是改变 accepts 18 | 19 | ## axios 20 | 21 | default post body application/json 22 | 23 | auto parse response application/json, even parse text/html 24 | 25 | 根据 responseType 处理返回结果, responseType 默认 json, 还支持 text 26 | 27 | axios 始终尝试用 JSON.parse 来解析 body, 无视 responseType 28 | 29 | response object 30 | 31 | status 32 | headers 33 | data 34 | statusText 35 | config 36 | 37 | post 发送 string 自动使用 application/x-www-form-urlencoded 38 | 39 | ## fetch 40 | 41 | 返回 response 对象 42 | 43 | fetch 无视 response content-type 44 | 45 | ## ant-design-pro 46 | 47 | 封装了一个 `request.js`, 主要特色是默认返回 `response.json()`, 需要自己传入 formdata 48 | 49 | ## 特殊格式 50 | 51 | - URLSearchParams 52 | - FormData, file, blob 53 | 54 | ## 参考文档 55 | 56 | - `$.ajax` 57 | - `axios` 58 | - `request` 59 | - `github.fetch` 60 | - `fetch standard` 61 | - `ant-design-pro request.js` 62 | - superagent 63 | - 小程序 64 | - 快应用 65 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | var _ = require('min-util') 2 | 3 | var CONTENT_TYPE_KEY = 'Content-Type' 4 | var reContentType = new RegExp(CONTENT_TYPE_KEY, 'i') 5 | 6 | function getContentType(headers) { 7 | var headerKeys = _.keys(headers) 8 | var typeKey = _.find(headerKeys, function(key) { 9 | return reContentType.test(key) 10 | }) 11 | return headers[typeKey] 12 | } 13 | 14 | function parseHeadersFromXhr(xhr) { 15 | return _.chain(xhr.getAllResponseHeaders()) 16 | .trim() 17 | .split('\n') 18 | .reduce(function(ret, header) { 19 | var i = _.indexOf(header, ':') 20 | var key = _.toLower(_.trim(_.slice(header, 0, i))) 21 | var value = _.trim(_.slice(header, i + 1)) 22 | if (ret[key]) { 23 | ret[key] = ',' + value // 多个 cookie 用 `,` 分隔, 无空格 24 | } else { 25 | ret[key] = value 26 | } 27 | return ret 28 | }, {}) 29 | .value() 30 | } 31 | 32 | function isFormData(val) { 33 | return (typeof FormData !== 'undefined') && (val instanceof FormData) 34 | } 35 | 36 | function timeout(time) { 37 | return new Promise(function(resolve, reject) { 38 | if (timeout) { 39 | setTimeout(function() { 40 | reject(new Error('timeout')) 41 | }, time) 42 | } 43 | }) 44 | } 45 | 46 | function clearTimer(timer) { 47 | if (timer) { 48 | clearTimeout(timer) 49 | } 50 | } 51 | 52 | function createError(message, obj) { 53 | var err = new Error(message) 54 | _.extend(err, obj) 55 | return err 56 | } 57 | 58 | exports.CONTENT_TYPE_KEY = CONTENT_TYPE_KEY 59 | exports.getContentType = getContentType 60 | exports.parseHeadersFromXhr = parseHeadersFromXhr 61 | exports.isFormData = isFormData 62 | exports.timeout = timeout 63 | exports.clearTimer = clearTimer 64 | exports.createError = createError 65 | -------------------------------------------------------------------------------- /comparison.md: -------------------------------------------------------------------------------- 1 | # 对比其他网络库 2 | 3 | 如果你看到这一篇, 说明你可能在选择请求库上有一定疑惑, 这么多网络请求的方式, 应该选哪个好? 4 | 5 | 本文尽可能的对比作者所使用过的请求库, 并作出对比, 希望可以给做出选择提供帮助 6 | 7 | 当然作为作者, 我们更偏爱 @chunpu/http, 它和实际项目更为贴合, 使用起来更加顺手 8 | 9 | ## axios 10 | 11 | axios 是 @chunpu/http 借鉴的库, 他们都有 12 | 13 | - 拦截器 14 | - 提供了新建实例的方法 15 | - 默认自动序列化请求数据 16 | - 支持 adapter 17 | - 默认发送 `application/json` 的 mime 数据格式 18 | - 支持设置 baseURL 19 | - 总是用 `JSON.parse` 解析返回数据 20 | 21 | axios 是非常棒的一个库, 在设计 @chunpu/http 之时, 我们直接使用了类似 axios 这样的 api 22 | 23 | axios 对于处理 `application/x-www-form-urlencoded` 这样的请求很不方便 24 | 25 | axios 的建议是在浏览器使用 URLSearchParams, 但显然我们希望我们在各平台享受完全一致的开发体验, @chunpu/http 完全不建议使用 URLSearchParams, 而是选择默认使用 `qs.stringify` 来序列化对象数据, 但局限是内置的 qs stringify 只支持扁平数据, 不过这已经可以满足绝大部分的需求了 26 | 27 | axios 还支持配置 paramsSerializer TODO 没试过 28 | 29 | ### 接口设计 30 | 31 | 我们先看看 http 本身传输的数据长什么样 32 | 33 | 发送请求 34 | 35 | ``` 36 | GET / HTTP/1.1 37 | Host: baidu.com 38 | User-Agent: curl/7.54.0 39 | Accept: */* 40 | ``` 41 | 42 | 返回请求 43 | 44 | ``` 45 | HTTP/1.1 200 OK 46 | Date: Sun, 30 Sep 2018 09:36:26 GMT 47 | Server: Apache 48 | Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT 49 | ETag: "51-47cf7e6ee8400" 50 | Accept-Ranges: bytes 51 | Content-Length: 81 52 | Cache-Control: max-age=86400 53 | Expires: Mon, 01 Oct 2018 09:36:26 GMT 54 | Connection: Keep-Alive 55 | Content-Type: text/html 56 | ``` 57 | 58 | 在发请求中, 我们最关心的是 method, url (http 第一行), 以及请求数据 59 | 60 | 因此我们把发送请求设计为 61 | 62 | ```js 63 | http.get('/path') 64 | http.post('/path', data) 65 | http.put('/path', data) 66 | ``` 67 | 68 | 在 http 返回中, 我们最关心的是 status (http 第一行), 以及返回的数据 69 | 70 | 因此我们设计一个通用的 response 对象, 结构是 71 | 72 | ```js 73 | { 74 | status: 200, 75 | headers: {}, 76 | data: {} 77 | } 78 | ``` 79 | 80 | ### 取消请求 81 | 82 | axios 使用了一个被撤销的 [cancelable promises proposal](https://github.com/tc39/proposal-cancelable-promises) 提案, 取消请求写法依赖 cancelToken 83 | 84 | [axios cancellation](https://github.com/axios/axios#cancellation) 85 | 86 | axios cancel 之后会在 response 的 interceptors 中触发 error, 但 promise 返回的是 resolved 状态 87 | 88 | ## jQuery 89 | 90 | jQuery 的 ajax 在之前的时代非常好用, jQuery 也提供了直接通过方法发送请求的接口 91 | 92 | ```js 93 | $.get('/path') 94 | $.post('/path') 95 | ``` 96 | 97 | 但 jQuery 并不支持 `$.put`, `$.delete` 等, $ 本身作为一个操作 dom 的库, 直接使用 `.delete` 有语义上的歧义, 事实上 `$.get` 也有一定的歧义, 初学者可能无法一下子看出这个 get 是 get/set 的 get, 还是 http method 的 get 98 | 99 | jQuery 混淆了 url query params 和 request body 的概念 100 | 101 | jQuery 并不是真正的 promise, then 中会返回三个参数, 对于封装 promise 会有很多困惑 102 | 103 | jQuery 取消请求会触发 `error` 104 | 105 | ```js 106 | var xhr = $.ajax('/api') 107 | xhr.then((...args) => console.log('success', ...args), (...args) => console.log('fail', ...args)) 108 | xhr.abort() 109 | ``` 110 | 111 | ## fetch 112 | 113 | fetch 最大的特色是默认提供 promise 的使用方法, 但这并不是说 fetch 就是一个完善的数据请求方法 114 | 115 | fetch 并不支持 timeout 116 | 117 | fetch 始终需要自己的 `response.json()`, 而且这依然是一个 promise 118 | 119 | 事实上, 几乎所有的 http api 接口都是按照 json 的格式来提供数据的, 我们就应该默认解析 json 120 | 121 | [github/fetch](https://github.com/github/fetch) 的 [Aborting requests](https://github.com/github/fetch#aborting-requests) 非常复杂, 而且用了 Dom Api 122 | 123 | 124 | ## 小程序和快应用 125 | 126 | 小程序和快应用同样提供了自己的 http 请求方式, 但他们都没有提供 promise 的请求方式, 仍然需要封装 127 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Promised Based Http Client for Real Project 6 | 7 | 8 | 9 | 10 | 11 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /src/http.js: -------------------------------------------------------------------------------- 1 | var _ = require('min-util') 2 | var Url = require('min-url') 3 | var qs = require('min-qs') 4 | var Queue = require('./queue') 5 | var utils = require('./utils') 6 | var adapters = require('./adapters') 7 | var CancelToken = require('./canceltoken') 8 | 9 | var JSON_TYPE = 'application/json' 10 | var URL_TYPE = 'application/x-www-form-urlencoded' 11 | var CONTENT_TYPE_KEY = utils.CONTENT_TYPE_KEY 12 | var simpleMethods = ['get', 'head', 'delete', 'options'] 13 | var dataMethods = ['post', 'put', 'patch'] 14 | var httpMethods = [].concat(simpleMethods, dataMethods) 15 | 16 | function HttpClient (opt) { 17 | var me = this 18 | this.defaults = { 19 | baseURL: '', 20 | timeout: 0, 21 | headers: { 22 | common: {} 23 | }, 24 | withCredentials: false 25 | } 26 | _.each(httpMethods, function(method) { 27 | var header = me.defaults.headers[method] = {} 28 | if (_.includes(dataMethods, 'method')) { 29 | header[method] = JSON_TYPE 30 | } 31 | }) 32 | 33 | this.interceptors = { 34 | request: new Queue(), 35 | response: new Queue() 36 | } 37 | 38 | this.qs = qs 39 | this.Promise = Promise 40 | this.CancelToken = CancelToken 41 | this.init(opt) 42 | } 43 | 44 | HttpClient.qs = qs 45 | 46 | var proto = HttpClient.prototype 47 | 48 | proto.init = function (opt) { 49 | // not exist in axios 50 | opt = _.extend({}, opt) 51 | this.defaults.headers.common = opt.headers || {} 52 | delete opt.headers 53 | _.extend(this.defaults, opt) 54 | } 55 | 56 | proto.create = function (opt) { 57 | return new HttpClient(opt) 58 | } 59 | 60 | proto.request = function (arg1, arg2) { 61 | var me = this 62 | if (_.isString(arg1)) { 63 | return this.request(_.extend({url: arg1}, arg2)) 64 | } 65 | var config = arg1 || {} 66 | config.headers = config.headers || {} // 必须有值 67 | config = _.extend({}, this.defaults, config) 68 | 69 | var url = config.baseURL + config.url 70 | url = Url.appendQuery(url, config.params) 71 | 72 | var method = _.toLower(config.method) || 'get' 73 | var defaultHeaders = this.defaults.headers 74 | var headers = _.extend({}, defaultHeaders.common, defaultHeaders[method], config.headers) 75 | var contentType = utils.getContentType(headers) 76 | var guessRequestType = contentType 77 | 78 | // 序列化 request data 79 | var data = config.data 80 | if (_.isPlainObject(data)) { 81 | // 只序列化 plain object, 其他直接发送, 比如字符串, FormData, Blob 之类的 82 | if (contentType === URL_TYPE) { 83 | data = qs.stringify(data) 84 | } else if (contentType === JSON_TYPE) { 85 | data = JSON.stringify(data) 86 | } 87 | if (!guessRequestType) { 88 | if (_.isString(data)) { 89 | guessRequestType = URL_TYPE 90 | } 91 | } 92 | if (!_.isString(data)) { 93 | data = JSON.stringify(data) // 默认用 json 94 | guessRequestType = guessRequestType || JSON_TYPE 95 | } 96 | if (!contentType && guessRequestType) { 97 | headers[CONTENT_TYPE_KEY] = guessRequestType 98 | } 99 | } else { 100 | if (utils.isFormData(data)) { 101 | // Let the browser set it 102 | delete headers[CONTENT_TYPE_KEY] 103 | } 104 | } 105 | 106 | var timeout = config.timeout 107 | 108 | // TODO auth 109 | config = { 110 | url: url, 111 | data: data, 112 | headers: headers, 113 | method: _.toUpper(method), 114 | cancelToken: config.cancelToken, 115 | withCredentials: config.withCredentials 116 | } 117 | 118 | if (timeout) { 119 | config.timeout = timeout 120 | } 121 | 122 | var ret = Promise.resolve(config) 123 | ret = me.interceptors.request.intercept(ret) // after get config 124 | .then(function(config) {return me.adapter.call(me, config)}) 125 | .then(function(response) { 126 | // 尝试解析 response.data, 总是尝试解析成 json(就像 axios 一样), 因为后端通常写不对 mime 127 | if (_.isString(response.data)) { 128 | if (!me.axios) { 129 | var rawResponse = response.data 130 | try { 131 | response.data = JSON.parse(response.data) 132 | } catch (err) { 133 | response.data = rawResponse 134 | } 135 | } 136 | } 137 | response.config = config 138 | response.headers = _.mapKeys(response.headers, function(value, key) { 139 | return _.toLower(key) // All header names are lower cased 140 | }) 141 | return response 142 | }) 143 | ret = me.interceptors.response.intercept(ret) // after parse data 144 | return ret 145 | } 146 | 147 | // axios adapter 148 | proto.adapter = function (config) { 149 | var defaults = this.defaults 150 | if (defaults.wx) { 151 | return adapters.wx.call(this, config) 152 | } else if (defaults.axios) { 153 | return adapters.axios.call(this, config) 154 | } else if (defaults.jQuery) { 155 | return adapters.jquery.call(this, config) 156 | } else if (defaults.quickapp) { 157 | return adapters.quickapp.call(this, config) 158 | } else if (typeof XMLHttpRequest === 'function') { 159 | return adapters.xhr.call(this, config) 160 | } 161 | } 162 | 163 | // TODO add http.all http.spread like axios 164 | 165 | _.each(simpleMethods, function(method) { 166 | proto[method] = function (url, config) { 167 | return this.request(_.extend({ 168 | method: method, 169 | url: url 170 | }, config)) 171 | } 172 | }) 173 | 174 | _.each(dataMethods, function(method) { 175 | proto[method] = function (url, data, config) { 176 | return this.request(_.extend({ 177 | url: url, 178 | method: method, 179 | data: data 180 | }, config)) 181 | } 182 | }) 183 | 184 | module.exports = exports = HttpClient 185 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('min-util') 2 | const httpClient = require('../') 3 | const assert = require('assert') 4 | 5 | var mockTimeout = 0.2 * 1000 6 | 7 | var wx = { 8 | request (opt) { 9 | var url = opt.url 10 | setTimeout(() => { 11 | if (_.includes(url, 'success')) { 12 | opt.success({ 13 | data: { 14 | code: 0, 15 | message: 'ok', 16 | data: { 17 | 18 | } 19 | }, 20 | statusCode: 200, 21 | header: { 22 | 'Content-Type': 'application/json' 23 | } 24 | }) 25 | } else { 26 | opt.fail() 27 | } 28 | }, mockTimeout) 29 | } 30 | } 31 | 32 | var quickapp = { 33 | fetch (opt) { 34 | var url = opt.url 35 | setTimeout(() => { 36 | if (_.includes(url, 'success')) { 37 | opt.success({ 38 | data: { 39 | code: 0, 40 | message: 'ok', 41 | data: { 42 | } 43 | }, 44 | code: 200, 45 | headers: { 46 | 'Content-Type': 'application/json' 47 | } 48 | }) 49 | } else { 50 | opt.fail({}, 503) 51 | } 52 | }, mockTimeout) 53 | } 54 | } 55 | 56 | var jQuery = { 57 | ajax (opt) { 58 | var url = opt.url 59 | setTimeout(() => { 60 | if (_.includes(url, 'success')) { 61 | opt.success({ 62 | code: 0, 63 | message: 'ok', 64 | data: { 65 | 66 | } 67 | }, 'success', { 68 | status: 200, 69 | getAllResponseHeaders () { 70 | return ` 71 | date: Fri, 07 Sep 2018 09:25:39 GMT 72 | etag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" 73 | connection: keep-alive 74 | x-powered-by: Express 75 | content-length: 2 76 | content-type: application/json; charset=utf-8 77 | ` 78 | } 79 | }) 80 | } else { 81 | opt.error({}, 503) 82 | } 83 | }, mockTimeout) 84 | } 85 | } 86 | 87 | var axios = { 88 | request (opt) { 89 | return new Promise((resolve, reject) => { 90 | var url = opt.url 91 | if (_.includes(url, 'success')) { 92 | var response = { 93 | data: { 94 | code: 0, 95 | message: 'ok', 96 | data: { 97 | } 98 | }, 99 | status: 200, 100 | headers: { 101 | 'Content-Type': 'application/json' 102 | } 103 | } 104 | resolve(response) 105 | } else { 106 | reject({}) 107 | } 108 | }) 109 | } 110 | } 111 | 112 | function testTimeout() { 113 | var wxHttp = httpClient.create({wx}) 114 | 115 | wxHttp.interceptors.response.use(response => { 116 | return response 117 | }, err => { 118 | assert(/timeout/.test(err.message), '应该包含 timeout') 119 | err.message = 'Server Timeout!' 120 | return Promise.reject(err) 121 | }) 122 | wxHttp.post('/success', null, { 123 | timeout: 10 124 | }).then(() => { 125 | assert(false, '应该超时') 126 | }).catch(err => { 127 | assert.deepEqual(err.message, 'Server Timeout!') 128 | }) 129 | 130 | var quickappHttp = httpClient.create({quickapp}) 131 | quickappHttp.post('/success', null, { 132 | timeout: 10 133 | }).then(() => { 134 | assert(false, '应该超时') 135 | }).catch(err => { 136 | assert(/timeout/.test(err.message), '应该包含 timeout') 137 | }) 138 | } 139 | 140 | function testMultiInterceptors() { 141 | var http = httpClient.create({wx}) 142 | http.interceptors.response.use(response => { 143 | response.x = 1 144 | return response 145 | }) 146 | http.interceptors.response.use(response => { 147 | assert.deepEqual(response.x, 1) 148 | response.x++ 149 | return response 150 | }) 151 | http.interceptors.response.use(response => { 152 | assert.deepEqual(response.x, 2) 153 | response.x++ 154 | return response 155 | }) 156 | http.post('/success', null, { 157 | timeout: 10000 158 | }).then(response => { 159 | assert.deepEqual(response.x, 3) 160 | }).catch(() => { 161 | assert(false) 162 | }) 163 | } 164 | 165 | function test(opt) { 166 | var type = _.keys(opt)[0] 167 | var timeout = mockTimeout * 2 168 | var http = httpClient.create(_.extend({ 169 | baseURL: 'http://my.domain', 170 | timeout 171 | }, opt)) 172 | 173 | assert.deepEqual(http.qs.stringify({query: 'string'}), 'query=string') 174 | // var http = httpClient 175 | // http.defaults.timeout = timeout 176 | // http.defaults.baseURL = 'http://my.domain' 177 | // _.extend(http.defaults, opt) 178 | 179 | http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' 180 | 181 | http.interceptors.request.use(config => { 182 | // console.log(type, 'config', config) 183 | assert.deepEqual(config.url, 'http://my.domain/success?query=string') 184 | assert.deepEqual(config.timeout, timeout) 185 | assert.deepEqual(config.method, 'POST') 186 | var contentType = config.headers['Content-Type'] 187 | if (contentType === 'application/x-www-form-urlencoded') { 188 | assert.deepEqual(config.data, 'key=value') 189 | } else { 190 | assert.deepEqual(config.data, '{"key":"value"}') 191 | } 192 | return config 193 | }) 194 | 195 | http.interceptors.response.use(response => { 196 | // console.log(type, 'response', response) 197 | assert.deepEqual(response.status, 200) 198 | assert.deepEqual(response.data, { 199 | code: 0, 200 | message: 'ok', 201 | data: {} 202 | }) 203 | assert(_.isObject(response.headers)) 204 | assert(_.isObject(response.config)) 205 | return response 206 | }) 207 | 208 | // 字符串也支持 209 | http.post('/success', { 210 | key: 'value' 211 | }, { 212 | params: { 213 | query: 'string' 214 | } 215 | }).then(response => { 216 | assert.deepEqual(response.status, 200) 217 | assert.deepEqual(response.data, { 218 | code: 0, 219 | message: 'ok', 220 | data: {} 221 | }) 222 | assert(_.isObject(response.headers)) 223 | assert(_.isObject(response.config)) 224 | }).catch(err => { 225 | console.log('error', err) 226 | assert(false, 'has error!') 227 | }) 228 | 229 | http.post('/success', 'key=value', { 230 | params: 'query=string' 231 | }).then(response => { 232 | assert.deepEqual(response.status, 200) 233 | assert.deepEqual(response.data, { 234 | code: 0, 235 | message: 'ok', 236 | data: {} 237 | }) 238 | assert(_.isObject(response.headers)) 239 | assert(_.isObject(response.config)) 240 | }).catch(err => { 241 | console.log('error', err) 242 | assert(false, 'has error!') 243 | }) 244 | } 245 | 246 | test({jQuery}) 247 | test({wx}) 248 | test({quickapp}) 249 | test({axios}) 250 | testTimeout() 251 | testMultiInterceptors() 252 | -------------------------------------------------------------------------------- /.readme.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/chunpu/http.svg?style=svg)](https://circleci.com/gh/chunpu/http) 2 | 3 | Features 4 | --- 5 | 6 | - [Largely axios-compatible API](#api) 7 | - Support More platforms like [微信小程序](#微信小程序), [快应用](#快应用) 8 | - Support the Promise API 9 | - [Intercept request and response](#interceptors) 10 | - [More Automatic Transform request and response data](#automatic-transform-request-data) 11 | - [Batch Cancel requests](#cancel-requests) 12 | - More adapters inside: [axios](#axios-nodejs), [jQuery](#jquery--zepto), XMLHttpRequest 13 | 14 | Inspired by [axios](https://github.com/axios/axios) 15 | 16 | Send Http Request just like axios in `微信小程序`, `快应用`, `jQuery`, or `XMLHttpRequest` by default 17 | 18 | Let's have the Same Experience with Request Data😜 19 | 20 | Usage 21 | --- 22 | 23 | ```js 24 | import http from '@chunpu/http' 25 | 26 | http.init({ 27 | baseURL: 'https://my.domain' 28 | }) 29 | 30 | http.get('/data').then(({data}) => { 31 | console.log(data) 32 | }) 33 | ``` 34 | 35 | ### Create Custom Http Instance 36 | 37 | ```js 38 | const anotherHttp = http.create({ 39 | baseURL: 'https://my.domain' 40 | }) 41 | ``` 42 | 43 | 44 | Api 45 | --- 46 | 47 | ### Simple Request 48 | 49 | - `.get(url, config)` 50 | - `.delete(url, config)` 51 | - `.head(url, config)` 52 | - `.options(url, config)` 53 | 54 | > Careful! There is no such api like `.get(url, params)` 55 | 56 | ### Request with Data 57 | 58 | - `.post(url, data, config)` 59 | - `.put(url, data, config)` 60 | - `.patch(url, data, config)` 61 | 62 | ### Basic Request 63 | 64 | - `.request(config)` 65 | - `.request(url, config)` 66 | 67 | All config param is not required 68 | 69 | 70 | Request Object 71 | --- 72 | 73 | - `data` data for request body 74 | - `headers` request headers 75 | - `method` request http method, default `GET` 76 | - `params` the url querystring object 77 | - `timeout` request timeout, 支持快应用和微信小程序 78 | - `withCredentials` whether use cors, default `false` 79 | 80 | ### Automatic Transform Request Data 81 | 82 | Respect the request `headers['content-type']` setting, data will be transform by the content type, Plain Object data will be auto stringify 83 | 84 | - `application/json` will `JSON.stringify` the data object 85 | - `application/x-www-form-urlencoded` will `qs.stringify` the data object 86 | 87 | data also support FormData, Blob, String 88 | 89 | 90 | Response Object 91 | --- 92 | 93 | - `data` response data 94 | - `headers` `name: value` headers, all header names are lower cased 95 | - `status` status code, number 96 | - `config` the request object 97 | 98 | Not Respect the response `headers['content-type']` value, will always try to `JSON.parse` the data, because most server not respect the response mime 99 | 100 | 101 | Platform Support 102 | --- 103 | 104 | ### 微信小程序 105 | 106 | ```js 107 | import http from '@chunpu/http' 108 | 109 | http.init({ 110 | baseURL: 'https://my.domain', 111 | wx: wx 112 | }) 113 | 114 | http.get('/data').then(({data}) => { 115 | console.log(data) 116 | }) 117 | ``` 118 | 119 | 支持单个请求超时设置 120 | 121 | > 请通过 npm 安装, 参见 [npm 支持](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html) 122 | 123 | ### 快应用 124 | 125 | ```js 126 | import http from '@chunpu/http' 127 | import fetch from '@system.fetch' 128 | 129 | http.init({ 130 | baseURL: 'https://my.domain', 131 | quickapp: fetch 132 | }) 133 | ``` 134 | 135 | 支持单个请求超时设置 136 | 137 | 记得在 `manifest.json` 文件中加入权限 138 | 139 | ```json 140 | "features": [ 141 | { "name": "system.network" }, 142 | { "name": "system.fetch" } 143 | ] 144 | ``` 145 | 146 | ### axios (node.js) 147 | 148 | ```js 149 | const axios = require('axios') 150 | import http from '@chunpu/http' 151 | 152 | http.init({ 153 | baseURL: 'https://my.domain', 154 | axios: axios 155 | }) 156 | ``` 157 | 158 | Please use http with `axios mode` in Node.js platform 159 | 160 | ### jQuery / Zepto 161 | 162 | ```js 163 | import http from '@chunpu/http' 164 | 165 | http.init({ 166 | baseURL: 'https://my.domain', 167 | jQuery: $ 168 | }) 169 | ``` 170 | 171 | 172 | Config Defaults / Init 173 | --- 174 | 175 | ```js 176 | // support axios style 177 | http.defaults.baseURL = 'https://my.domain' 178 | http.defaults.timeout = 1000 * 20 179 | 180 | // can also use http.init 181 | http.init({ 182 | baseURL: 'https://my.domain', 183 | timeout: 1000 * 20 184 | }) 185 | ``` 186 | 187 | > Config default Post request `Content-Type` 188 | 189 | default is `JSON` 190 | 191 | Always stringify Data to `JSON` 192 | 193 | ```js 194 | http.defaults.headers.post['Content-Type'] = 'application/json' 195 | ``` 196 | 197 | Always stringify Data to `querystring`, which can really work not like axios... 198 | 199 | ```js 200 | http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' 201 | ``` 202 | 203 | Interceptors 204 | --- 205 | 206 | ```js 207 | import http from '@chunpu/http' 208 | 209 | http.init({ 210 | baseURL: 'https://my.domain' 211 | }) 212 | 213 | http.interceptors.request.use(config => { 214 | // Do something before request is sent 215 | return config 216 | }) 217 | 218 | http.interceptors.response.use(response => { 219 | // Do something with response 220 | return response 221 | }) 222 | ``` 223 | 224 | 225 | Cancel Requests 226 | --- 227 | 228 | compatible with axios Cancellation 229 | 230 | For easy understanding, `cancelToken` equals `deferred` equals `source.token` 231 | 232 | ```js 233 | const source = http.CancelToken.source() 234 | 235 | http.get('/very/slow/api/1', { 236 | cancelToken: source.token 237 | }).catch(err => { 238 | console.error(err) // error: cancel request 239 | }) 240 | 241 | http.get('/very/slow/api/2', { 242 | cancelToken: source.token 243 | }).catch(err => { 244 | console.error(err) // error: cancel request 245 | }) 246 | 247 | setTimeout(() => { 248 | source.cancel('cancel request') // will cancel all requests with this source 249 | }, 1000) 250 | ``` 251 | 252 | 253 | Usage With Real Project 254 | --- 255 | 256 | Assume the `my.domain` service always return data like this 257 | 258 | ```js 259 | { 260 | code: 0, 261 | message: 'ok', 262 | data: { 263 | key: 'value' 264 | } 265 | } 266 | ``` 267 | 268 | ```js 269 | import http from '@chunpu/http' 270 | 271 | http.init({ 272 | baseURL: 'https://my.domain' 273 | }) 274 | 275 | http.interceptors.response.use(response => { 276 | if (typeof response.data === 'object') { 277 | // always spread the response data for directly usage 278 | Object.assign(response, response.data) 279 | } 280 | return response 281 | }) 282 | 283 | http.post('/user/1024', { 284 | name: 'Tony' 285 | }).then(({data, code, message}) => { 286 | if (code === 0) { 287 | return data 288 | } else { 289 | console.error('error', message) 290 | } 291 | }) 292 | ``` 293 | 294 | Usage with Vue.js 295 | --- 296 | 297 | ```js 298 | import http from '@chunpu/http' 299 | 300 | Vue.prototype.$http = http 301 | 302 | // in vue component file 303 | submit () { 304 | this.$http.post('/user/1024', {name: 'Tony'}).then(({data}) => { 305 | this.user = data 306 | }) 307 | } 308 | ``` 309 | 310 | Handling Errors 311 | --- 312 | 313 | All Platform support timeout error for one request 314 | 315 | ```js 316 | http.get('/very/slow/api').catch(err => { 317 | if (/timeout/i.test(err.message)) { 318 | // this is timeout error 319 | } 320 | }) 321 | ``` 322 | 323 | Other Api 324 | --- 325 | 326 | You can stringify query string by 327 | 328 | ```js 329 | import http from '@chunpu/http' 330 | 331 | http.qs.stringify({ 332 | query: 'string' 333 | }) 334 | // => 'query=string' 335 | ``` 336 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | @chunpu/http 2 | === 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![Downloads][downloads-image]][downloads-url] 6 | [![Dependency Status][david-image]][david-url] 7 | 8 | [npm-image]: https://img.shields.io/npm/v/@chunpu/http.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/@chunpu/http 10 | [downloads-image]: http://img.shields.io/npm/dm/@chunpu/http.svg?style=flat-square 11 | [downloads-url]: https://npmjs.org/package/@chunpu/http 12 | [david-image]: http://img.shields.io/david/chunpu/http.svg?style=flat-square 13 | [david-url]: https://david-dm.org/chunpu/http 14 | 15 | 16 | Promise Based request / fetch / http For Real Project, Support multiple platforms 17 | 18 | Installation 19 | --- 20 | 21 | ```sh 22 | npm i @chunpu/http 23 | ``` 24 | 25 | [![CircleCI](https://circleci.com/gh/chunpu/http.svg?style=svg)](https://circleci.com/gh/chunpu/http) 26 | 27 | Features 28 | --- 29 | 30 | - [Largely axios-compatible API](#api) 31 | - Support More platforms like [微信小程序](#微信小程序), [快应用](#快应用) 32 | - Support the Promise API 33 | - [Intercept request and response](#interceptors) 34 | - [More Automatic Transform request and response data](#automatic-transform-request-data) 35 | - [Batch Cancel requests](#cancel-requests) 36 | - More adapters inside: [axios](#axios-nodejs), [jQuery](#jquery--zepto), XMLHttpRequest 37 | 38 | Inspired by [axios](https://github.com/axios/axios) 39 | 40 | Send Http Request just like axios in `微信小程序`, `快应用`, `jQuery`, or `XMLHttpRequest` by default 41 | 42 | Let's have the Same Experience with Request Data😜 43 | 44 | Usage 45 | --- 46 | 47 | ```js 48 | import http from '@chunpu/http' 49 | 50 | http.init({ 51 | baseURL: 'https://my.domain' 52 | }) 53 | 54 | http.get('/data').then(({data}) => { 55 | console.log(data) 56 | }) 57 | ``` 58 | 59 | ### Create Custom Http Instance 60 | 61 | ```js 62 | const anotherHttp = http.create({ 63 | baseURL: 'https://my.domain' 64 | }) 65 | ``` 66 | 67 | 68 | Api 69 | --- 70 | 71 | ### Simple Request 72 | 73 | - `.get(url, config)` 74 | - `.delete(url, config)` 75 | - `.head(url, config)` 76 | - `.options(url, config)` 77 | 78 | > Careful! There is no such api like `.get(url, params)` 79 | 80 | ### Request with Data 81 | 82 | - `.post(url, data, config)` 83 | - `.put(url, data, config)` 84 | - `.patch(url, data, config)` 85 | 86 | ### Basic Request 87 | 88 | - `.request(config)` 89 | - `.request(url, config)` 90 | 91 | All config param is not required 92 | 93 | 94 | Request Object 95 | --- 96 | 97 | - `data` data for request body 98 | - `headers` request headers 99 | - `method` request http method, default `GET` 100 | - `params` the url querystring object 101 | - `timeout` request timeout, 支持快应用和微信小程序 102 | - `withCredentials` whether use cors, default `false` 103 | 104 | ### Automatic Transform Request Data 105 | 106 | Respect the request `headers['content-type']` setting, data will be transform by the content type, Plain Object data will be auto stringify 107 | 108 | - `application/json` will `JSON.stringify` the data object 109 | - `application/x-www-form-urlencoded` will `qs.stringify` the data object 110 | 111 | data also support FormData, Blob, String 112 | 113 | 114 | Response Object 115 | --- 116 | 117 | - `data` response data 118 | - `headers` `name: value` headers, all header names are lower cased 119 | - `status` status code, number 120 | - `config` the request object 121 | 122 | Not Respect the response `headers['content-type']` value, will always try to `JSON.parse` the data, because most server not respect the response mime 123 | 124 | 125 | Platform Support 126 | --- 127 | 128 | ### 微信小程序 129 | 130 | ```js 131 | import http from '@chunpu/http' 132 | 133 | http.init({ 134 | baseURL: 'https://my.domain', 135 | wx: wx 136 | }) 137 | 138 | http.get('/data').then(({data}) => { 139 | console.log(data) 140 | }) 141 | ``` 142 | 143 | 支持单个请求超时设置 144 | 145 | > 请通过 npm 安装, 参见 [npm 支持](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html) 146 | 147 | ### 快应用 148 | 149 | ```js 150 | import http from '@chunpu/http' 151 | import fetch from '@system.fetch' 152 | 153 | http.init({ 154 | baseURL: 'https://my.domain', 155 | quickapp: fetch 156 | }) 157 | ``` 158 | 159 | 支持单个请求超时设置 160 | 161 | 记得在 `manifest.json` 文件中加入权限 162 | 163 | ```json 164 | "features": [ 165 | { "name": "system.network" }, 166 | { "name": "system.fetch" } 167 | ] 168 | ``` 169 | 170 | ### axios (node.js) 171 | 172 | ```js 173 | const axios = require('axios') 174 | import http from '@chunpu/http' 175 | 176 | http.init({ 177 | baseURL: 'https://my.domain', 178 | axios: axios 179 | }) 180 | ``` 181 | 182 | Please use http with `axios mode` in Node.js platform 183 | 184 | ### jQuery / Zepto 185 | 186 | ```js 187 | import http from '@chunpu/http' 188 | 189 | http.init({ 190 | baseURL: 'https://my.domain', 191 | jQuery: $ 192 | }) 193 | ``` 194 | 195 | 196 | Config Defaults / Init 197 | --- 198 | 199 | ```js 200 | // support axios style 201 | http.defaults.baseURL = 'https://my.domain' 202 | http.defaults.timeout = 1000 * 20 203 | 204 | // can also use http.init 205 | http.init({ 206 | baseURL: 'https://my.domain', 207 | timeout: 1000 * 20 208 | }) 209 | ``` 210 | 211 | > Config default Post request `Content-Type` 212 | 213 | default is `JSON` 214 | 215 | Always stringify Data to `JSON` 216 | 217 | ```js 218 | http.defaults.headers.post['Content-Type'] = 'application/json' 219 | ``` 220 | 221 | Always stringify Data to `querystring`, which can really work not like axios... 222 | 223 | ```js 224 | http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' 225 | ``` 226 | 227 | Interceptors 228 | --- 229 | 230 | ```js 231 | import http from '@chunpu/http' 232 | 233 | http.init({ 234 | baseURL: 'https://my.domain' 235 | }) 236 | 237 | http.interceptors.request.use(config => { 238 | // Do something before request is sent 239 | return config 240 | }) 241 | 242 | http.interceptors.response.use(response => { 243 | // Do something with response 244 | return response 245 | }) 246 | ``` 247 | 248 | 249 | Cancel Requests 250 | --- 251 | 252 | compatible with axios Cancellation 253 | 254 | For easy understanding, `cancelToken` equals `deferred` equals `source.token` 255 | 256 | ```js 257 | const source = http.CancelToken.source() 258 | 259 | http.get('/very/slow/api/1', { 260 | cancelToken: source.token 261 | }).catch(err => { 262 | console.error(err) // error: cancel request 263 | }) 264 | 265 | http.get('/very/slow/api/2', { 266 | cancelToken: source.token 267 | }).catch(err => { 268 | console.error(err) // error: cancel request 269 | }) 270 | 271 | setTimeout(() => { 272 | source.cancel('cancel request') // will cancel all requests with this source 273 | }, 1000) 274 | ``` 275 | 276 | 277 | Usage With Real Project 278 | --- 279 | 280 | Assume the `my.domain` service always return data like this 281 | 282 | ```js 283 | { 284 | code: 0, 285 | message: 'ok', 286 | data: { 287 | key: 'value' 288 | } 289 | } 290 | ``` 291 | 292 | ```js 293 | import http from '@chunpu/http' 294 | 295 | http.init({ 296 | baseURL: 'https://my.domain' 297 | }) 298 | 299 | http.interceptors.response.use(response => { 300 | if (typeof response.data === 'object') { 301 | // always spread the response data for directly usage 302 | Object.assign(response, response.data) 303 | } 304 | return response 305 | }) 306 | 307 | http.post('/user/1024', { 308 | name: 'Tony' 309 | }).then(({data, code, message}) => { 310 | if (code === 0) { 311 | return data 312 | } else { 313 | console.error('error', message) 314 | } 315 | }) 316 | ``` 317 | 318 | Usage with Vue.js 319 | --- 320 | 321 | ```js 322 | import http from '@chunpu/http' 323 | 324 | Vue.prototype.$http = http 325 | 326 | // in vue component file 327 | submit () { 328 | this.$http.post('/user/1024', {name: 'Tony'}).then(({data}) => { 329 | this.user = data 330 | }) 331 | } 332 | ``` 333 | 334 | Handling Errors 335 | --- 336 | 337 | All Platform support timeout error for one request 338 | 339 | ```js 340 | http.get('/very/slow/api').catch(err => { 341 | if (/timeout/i.test(err.message)) { 342 | // this is timeout error 343 | } 344 | }) 345 | ``` 346 | 347 | Other Api 348 | --- 349 | 350 | You can stringify query string by 351 | 352 | ```js 353 | import http from '@chunpu/http' 354 | 355 | http.qs.stringify({ 356 | query: 'string' 357 | }) 358 | // => 'query=string' 359 | ``` 360 | 361 | License 362 | --- 363 | 364 | [![License][license-image]][license-url] 365 | 366 | [license-image]: http://img.shields.io/npm/l/@chunpu/http.svg?style=flat-square 367 | [license-url]: # 368 | -------------------------------------------------------------------------------- /dist/http.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @chunpu/http@2.1.0 by chunpu 3 | * 2018-10-31T03:54:43.944Z 4 | */ 5 | !function(n,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.http=t():n.http=t()}(window,function(){return function(n){var t={};function e(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return n[r].call(i.exports,i,i.exports,e),i.l=!0,i.exports}return e.m=n,e.c=t,e.d=function(n,t,r){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:r})},e.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},e.t=function(n,t){if(1&t&&(n=e(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(e.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var i in n)e.d(r,i,function(t){return n[t]}.bind(null,i));return r},e.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return e.d(t,"a",t),t},e.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},e.p="",e(e.s=8)}([function(n,t,e){n.exports=e(10)},function(n,t,e){var r=e(6),i=e(4);i.resolve=e(31),i.reject=e(32),i.prototype.catch=i.prototype.caught=function(n){return this.then(null,n)},i.prototype.delay=function(n){var t;return this.then(function(e){return t=e,i.delay(n)}).then(function(){return t})},i.delay=function(n){return new i(function(t){setTimeout(t,n)})},i.all=function(n){var t=[],e=r.size(n),u=0;return new i(function(o,c){0===e&&o(t),r.each(n,function(n,r){i.resolve(n).then(function(n){t[r]=n,++u===e&&o(t)},function(n){c(n)})})})},i.race=function(n){return new i(function(t,e){r.each(n,function(n){i.resolve(n).then(function(n){t(n)},function(n){e(n)})})})},n.exports=i},function(n,t,e){(function(n){var r=e(0),i=new RegExp("Content-Type","i");t.CONTENT_TYPE_KEY="Content-Type",t.getContentType=function(n){var t=r.keys(n);return n[r.find(t,function(n){return i.test(n)})]},t.parseHeadersFromXhr=function(n){return r.chain(n.getAllResponseHeaders()).trim().split("\n").reduce(function(n,t){var e=r.indexOf(t,":"),i=r.toLower(r.trim(r.slice(t,0,e))),u=r.trim(r.slice(t,e+1));return n[i]?n[i]=","+u:n[i]=u,n},{}).value()},t.isFormData=function(n){return"undefined"!=typeof FormData&&n instanceof FormData},t.timeout=function t(e){return new n(function(n,r){t&&setTimeout(function(){r(new Error("timeout"))},e)})},t.clearTimer=function(n){n&&clearTimeout(n)},t.createError=function(n,t){var e=new Error(n);return r.extend(e,t),e}}).call(this,e(1))},function(n,t,e){var r=e(11),i=[].slice,u=t;u.is=r,u.extend=u.assign=function(n){if(n){var t=i.call(arguments,1);a(t,function(t){h(t,function(t,e){r.undef(t)||(n[e]=t)})})}return n},u.each=a,u.map=function(n,t){var e=[];return a(n,function(n,r,i){e[r]=t(n,r,i)}),e},u.filter=function(n,t){var e=[];return a(n,function(n,r,i){t(n,r,i)&&e.push(n)}),e},u.some=function(n,t){return-1!=f(n,t)},u.every=function(n,t){return-1==f(n,l(t))},u.reduce=function(n,t,e){return a(n,function(r,i){e=t(e,r,i,n)}),e},u.findIndex=f,u.find=function(n,t){var e=u.findIndex(n,t);if(-1!=e)return n[e]},u.indexOf=p,u.includes=function(n,t){return-1!=p(n,t)},u.toArray=s,u.slice=function(n,t,e){var i=[],u=c(n);return u>=0&&(t=t||0,0!==e&&(e=e||u),r.fn(n.slice)||(n=s(n)),i=n.slice(t,e)),i},u.negate=l,u.forIn=h,u.keys=function(n){var t=[];return h(n,function(n,e){t.push(e)}),t};var o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function c(n){if(null!=n)return n.length}function a(n,t){var e=c(n);if(e&&r.fn(t))for(var i=0;i=0)return!0}return!1},e.window=function(n){return!(!n||n.window!=n)},e.empty=function(n){if(e.string(n)||e.arraylike(n))return 0===n.length;if(e.hash(n))for(var t in n)if(c(n,t))return!1;return!0},e.element=function(n){return!(!n||1!==n.nodeType)},e.regexp=function(n){return"regexp"==u(n)}}).call(this,e(5))},function(n,t){n.exports=function(n){var t=n.is;n.isString=t.string,n.isArray=t.array,n.isArrayLike=t.arraylike,n.isBoolean=t.bool,n.isElement=t.element,n.isEmpty=t.empty,n.isFunction=t.fn,n.isInteger=t.integer,n.isNaN=t.nan,n.isNumber=t.number,n.isObject=t.object,n.isPlainObject=t.plainObject,n.isRegExp=t.regexp,n.isString=t.string,n.isUndefined=t.undef}},function(n,t){n.exports=function(n){var t=n.is;n.now=function(){return+new Date},n.constant=function(n){return function(){return n}},n.identity=function(n){return n},n.random=function(n,t){return n+Math.floor(Math.random()*(t-n+1))},n.mixin=function(e,r,i){var u=n.functions(r);if(e)if(t.fn(e)){(i=i||{}).chain;var o=e.prototype;n.each(u,function(n){var t=r[n];o[n]=function(){var n=this,e=[n.__value];e.push.apply(e,arguments);var r=t.apply(n,e);return n.__chain?(n.__value=r,n):r}})}else n.each(u,function(n){e[n]=r[n]});return e},n.chain=function(t){var e=n(t);return e.__chain=!0,e},n.value=function(){return this.__chain=!1,this.__value}}},function(n,t){n.exports=function(n){var t=n.forEach=n.each,e=n.includes,r=n.is,i=Array.prototype;function u(t,e){var r=n.size(e);return t<0&&(t+=r),t<0&&(t=0),t>r&&(t=r),t||0}function o(t,e){var r=[],u=n.len(e);if(u)for(e=e.sort(function(n,t){return n-t});u--;){var o=e[u];r.push(i.splice.call(t,o,1)[0])}return r.reverse(),r}n.reject=function(t,e){return n.filter(t,function(n,t,r){return!e(n,t,r)})},n.without=function(t){var e=n.slice(arguments,1);return n.difference(t,e)},n.difference=function(t,r){var i=[];return n.each(t,function(n){e(r,n)||i.push(n)}),i},n.pluck=function(t,e){return n.map(t,function(n){if(n)return n[e]})},n.nth=function(t,e){return e=(e=u(e,t))||0,n.isString(t)?t.charAt(e):t[e]},n.first=function(t){if(t)return n.nth(t,0)},n.last=function(t){var e=n.len(t);if(e)return n.nth(t,e-1)},n.asyncMap=function(n,e,r){var i,u,o=[],c=0;t(n,function(t,a){u=!0,e(t,function(t,e){if(!i){if(c++,t)return i=!0,r(t);o[a]=e,c==n.length&&(i=!0,r(null,o))}})}),u||r(null)},n.uniq=function(t){return n.uniqBy(t)},n.uniqBy=function(n,i){var u=[],o=[];return r.fn(i)||(i=null),t(n,function(n){var t=n;i&&(t=i(n)),e(o,t)||(o.push(t),u.push(n))}),u},n.flatten=function(n){var e=[];return t(n,function(n){r.arraylike(n)?t(n,function(n){e.push(n)}):e.push(n)}),e},n.union=function(){return n.uniq(n.flatten(arguments))},n.sample=function(t,e){for(var r=n.toArray(t),i=r.length,u=Math.min(e||1,i),o=0;o=0?e=r:n.isObject(t)&&(e=n.keys(t).length)}return e}}},function(n,t){n.exports=function(n){var t=n.is,e=(n.each,n.forIn);n.only=function(e,r){return e=e||{},t.string(r)&&(r=r.split(/ +/)),n.reduce(r,function(n,t){return null!=e[t]&&(n[t]=e[t]),n},{})},n.values=function(t){return n.map(n.keys(t),function(n){return t[n]})},n.pick=function(r,i){if(!t.fn(i))return n.pick(r,function(n,t){return t==i});var u={};return e(r,function(n,t,e){i(n,t,e)&&(u[t]=n)}),u},n.functions=function(e){return n.keys(n.pick(e,function(n){return t.fn(n)}))},n.mapKeys=function(n,t){var r={};return e(n,function(n,e,i){var u=t(n,e,i);r[u]=n}),r},n.mapObject=n.mapValues=function(n,t){var r={};return e(n,function(n,e,i){r[e]=t(n,e,i)}),r},n.get=function(t,e){if((e=u(e)).length&&n.every(e,function(n){if(null!=t)return t=t[n],!0}))return t},n.has=function(e,r){if((r=u(r)).length&&n.every(r,function(n){if(null!=e&&t.owns(e,n))return e=e[n],!0}))return!0;return!1},n.set=function(e,r,i){r=u(r);var o=e;return n.every(r,function(n,e){if(t.oof(o)){if(e+1!=r.length){if(null==(u=o[n])){var u={};~~n==n&&(u=[])}return o=o[n]=u,!0}o[n]=i}}),e},n.create=function(){function t(){}return function(e,r){return"object"!=typeof e&&(e=null),t.prototype=e,n.extend(new t,r)}}(),n.defaults=function(){var e=arguments,r=e[0],i=n.slice(e,1);return r&&n.each(i,function(e){n.mapObject(e,function(n,e){t.undef(r[e])&&(r[e]=n)})}),r},n.isMatch=function(n,t){var r=!0;return n=n||{},e(t,function(t,e){if(t!==n[e])return r=!1,!1}),r},n.toPlainObject=function(n){var t={};return e(n,function(n,e){t[e]=n}),t},n.invert=function(n){var t={};return e(n,function(n,e){t[n]=e}),t};var r=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,i=/\\(\\)?/g;function u(e){if(t.array(e))return e;var u=[];return n.toString(e).replace(r,function(n,t,e,r){var o=t||n;e&&(o=r.replace(i,"$1")),u.push(o)}),u}}},function(n,t,e){n.exports=function(n){var t=n.is,r=n.slice;function i(n){var t=new i.Cache;function e(){var e=arguments,r=e[0];if(!t.has(r)){var i=n.apply(this,e);t.set(r,i)}return t.get(r)}return e.cache=t,e}n.bind=function(e,i){if(t.string(i)){var u=e;e=u[i],i=u}if(!t.fn(e))return e;var o=r(arguments,2);return i=i||this,function(){return e.apply(i,n.flatten([o,arguments]))}},n.inherits=function(t,e){t.super_=e,t.prototype=n.create(e.prototype,{constructor:t})},n.delay=function(t,e){var r=n.slice(arguments,2);return setTimeout(function(){t.apply(this,r)},e)},n.before=function(n,t){return function(){if(n>1)return n--,t.apply(this,arguments)}},n.once=function(t){return n.before(2,t)},n.after=function(n,t){return function(){if(!(n>1))return t.apply(this,arguments);n--}},n.throttle=function(t,e,r){return e=e||0,r=n.extend({leading:!0,trailing:!0,maxWait:e},r),n.debounce(t,e,r)},n.debounce=function(t,e,r){e=e||0;var i,u=(r=n.extend({leading:!1,trailing:!0},r)).maxWait,o=0,c=0,a=n.now();function f(t,e,r){return o=n.now(),t.apply(e,r)}function s(){i&&(clearTimeout(i),i=null)}function l(){var l=!((a=n.now())-o>e||u&&a-c>u);c=a;var p=this,h=arguments;s(),l?r.trailing&&(i=n.delay(function(){f(t,p,h)},e)):f(t,p,h)}return r.leading||(o=a),l.cancel=s,l},i.Cache=e(17),n.memoize=i,n.wrap=function(n,t){return function(){var e=[n];return e.push.apply(e,arguments),t.apply(this,e)}},n.curry=function(t){var e=t.length;return function r(i){return function(){var u=i.concat(n.slice(arguments));return u.length>=e?(u.length=e,t.apply(this,u)):r(u)}}([])}}},function(n,t,e){var r=e(3).is;function i(){this.data={}}n.exports=i;var u=i.prototype;u.has=function(n){return r.owns(this.data,n)},u.get=function(n){return this.data[n]},u.set=function(n,t){this.data[n]=t},u.delete=function(n){delete this.data[n]}},function(n,t){n.exports=function(n){n.tostr=n.toString=i;var t=n.indexOf;n.split=function(n,t,e){return(n=i(n)).split(t,e)},n.capitalize=function(n){return(n=i(n)).charAt(0).toUpperCase()+n.substr(1)},n.decapitalize=function(n){return(n=i(n)).charAt(0).toLowerCase()+n.substr(1)},n.camelCase=function(t){var e=(t=i(t)).split(/[^\w]|_+/);return e=n.map(e,function(t){return n.capitalize(t)}),n.decapitalize(e.join(""))},n.startsWith=function(n,e){return 0==t(n,e)},n.endsWith=function(t,e){return(e+="")==n.slice(t,n.len(t)-n.len(e))},n.toLower=n.lower=function(n){return i(n).toLowerCase()},n.toUpper=n.upper=function(n){return i(n).toUpperCase()},n.repeat=function(t,e){return n.map(n.range(e),function(){return t}).join("")},n.padStart=function(n,t,e){return n=i(n),r(e,(t=t||0)-n.length)+n},n.padEnd=function(n,t,e){return(n=i(n))+r(e,(t=t||0)-n.length)};var e={"&":"&","<":"<",">":">",'"':""","'":"'"};function r(t,e){t=i(t)||" ";var r=Math.floor(e/t.length)+1;return n.repeat(t,r).slice(0,e)}function i(n){return n||0==n?n+"":""}n.escape=function(n){return i(n).replace(/[&<>"']/g,function(n){return e[n]||n})},n.template=function(t){var e=['with(data) {var ret = ""'];n.each(n.split(t,"<%"),function(t,r){var i=t.split("%>");if(i[1])return function(t){var r=n.first(t);if("="===r||"-"===r){var i=n.slice(t,1);"-"===r&&(i="_.escape("+i+")"),e.push("ret += "+i)}else e.push(t)}(n.trim(i[0])),u(i[1]);u(i[0])}),e.push("return ret}");var r=new Function("data",e.join("\n")),i={_:n};return function(t){return r(n.extend({},i,t))};function u(n){e.push('ret += "'+n.replace(/('|"|\\)/g,"\\$1").replace(/\r/g,"\\r").replace(/\n/g,"\\n")+'"')}}}},function(n,t){n.exports=function(n){n.sum=function(t){return n.reduce(t,function(n,t){return n+t},0)},n.max=function(t,e){var r=-1,i=-1/0;return e=e||n.identity,n.each(t,function(n,t){(n=e(n))>i&&(i=n,r=t)}),r>-1?t[r]:i},n.min=function(t,e){var r=-1,i=1/0;return e=e||n.identity,n.each(t,function(n,t){(n=e(n))-1?t[r]:i}}},function(n,t,e){(function(t){var r=e(0),i=e(33),u=e(7),o=e(34),c=e(2),a=e(35),f=e(41),s="application/json",l="application/x-www-form-urlencoded",p=c.CONTENT_TYPE_KEY,h=["get","head","delete","options"],d=["post","put","patch"],v=[].concat(h,d);function m(n){var e=this;this.defaults={baseURL:"",timeout:0,headers:{common:{}},withCredentials:!1},r.each(v,function(n){var t=e.defaults.headers[n]={};r.includes(d,"method")&&(t[n]=s)}),this.interceptors={request:new o,response:new o},this.qs=u,this.Promise=t,this.CancelToken=f,this.init(n)}m.qs=u;var y=m.prototype;y.init=function(n){n=r.extend({},n),this.defaults.headers.common=n.headers||{},delete n.headers,r.extend(this.defaults,n)},y.create=function(n){return new m(n)},y.request=function(n,e){var o=this;if(r.isString(n))return this.request(r.extend({url:n},e));var a=n||{};a.headers=a.headers||{};var f=(a=r.extend({},this.defaults,a)).baseURL+a.url;f=i.appendQuery(f,a.params);var h=r.toLower(a.method)||"get",d=this.defaults.headers,v=r.extend({},d.common,d[h],a.headers),m=c.getContentType(v),y=m,g=a.data;r.isPlainObject(g)?(m===l?g=u.stringify(g):m===s&&(g=JSON.stringify(g)),y||r.isString(g)&&(y=l),r.isString(g)||(g=JSON.stringify(g),y=y||s),!m&&y&&(v[p]=y)):c.isFormData(g)&&delete v[p];var w=a.timeout;a={url:f,data:g,headers:v,method:r.toUpper(h),cancelToken:a.cancelToken,withCredentials:a.withCredentials},w&&(a.timeout=w);var x=t.resolve(a);return x=o.interceptors.request.intercept(x).then(function(n){return o.adapter.call(o,n)}).then(function(n){if(r.isString(n.data)&&!o.axios){var t=n.data;try{n.data=JSON.parse(n.data)}catch(e){n.data=t}}return n.config=a,n.headers=r.mapKeys(n.headers,function(n,t){return r.toLower(t)}),n}),x=o.interceptors.response.intercept(x)},y.adapter=function(n){var t=this.defaults;return t.wx?a.wx.call(this,n):t.axios?a.axios.call(this,n):t.jQuery?a.jquery.call(this,n):t.quickapp?a.quickapp.call(this,n):"function"==typeof XMLHttpRequest?a.xhr.call(this,n):void 0},r.each(h,function(n){y[n]=function(t,e){return this.request(r.extend({method:n,url:t},e))}}),r.each(d,function(n){y[n]=function(t,e,i){return this.request(r.extend({url:t,method:n,data:e},i))}}),n.exports=m}).call(this,e(1))},function(n,t,e){var r=e(3);function i(n){if(!(this instanceof i))return new i(n);this.__value=n,this.__chain=!1}n.exports=r.extend(i,r),e(22)(i),e(23)(i),e(24)(i),e(25)(i),e(26)(i),e(28)(i),e(29)(i),i.mixin(i,i)},function(n,t){n.exports=function(n){var t=n.is;n.isString=t.string,n.isArray=t.array,n.isArrayLike=t.arraylike,n.isBoolean=t.bool,n.isElement=t.element,n.isEmpty=t.empty,n.isFunction=t.fn,n.isInteger=t.integer,n.isNaN=t.nan,n.isNumber=t.number,n.isObject=t.object,n.isPlainObject=t.plainObject,n.isRegExp=t.regexp,n.isString=t.string,n.isUndefined=t.undef}},function(n,t){n.exports=function(n){var t=n.is;n.now=function(){return+new Date},n.constant=function(n){return function(){return n}},n.identity=function(n){return n},n.random=function(n,t){return n+Math.floor(Math.random()*(t-n+1))},n.mixin=function(e,r,i){var u=n.functions(r);if(e)if(t.fn(e)){i=i||{};var o=e.prototype;n.each(u,function(n){var t=r[n];o[n]=function(){var n=this,e=[n.__value];e.push.apply(e,arguments);var r=t.apply(n,e);return n.__chain?(n.__value=r,n):r}})}else n.each(u,function(n){e[n]=r[n]});return e},n.chain=function(t){var e=n(t);return e.__chain=!0,e},n.value=function(){return this.__chain=!1,this.__value};var e=0;n.uniqueId=function(t){return e++,n.toString(t)+e}}},function(n,t){n.exports=function(n){var t=n.forEach=n.each,e=n.includes,r=n.is,i=Array.prototype;function u(t,e){var r=n.size(e);return t<0&&(t+=r),t<0&&(t=0),t>r&&(t=r),t||0}function o(t,e){var r=[],u=n.len(e);if(u)for(e=e.sort(function(n,t){return n-t});u--;){var o=e[u];r.push(i.splice.call(t,o,1)[0])}return r.reverse(),r}n.reject=function(t,e){return n.filter(t,function(n,t,r){return!e(n,t,r)})},n.without=function(t){var e=n.slice(arguments,1);return n.difference(t,e)},n.difference=function(t,r){var i=[];return n.each(t,function(n){e(r,n)||i.push(n)}),i},n.pluck=function(t,e){return n.map(t,function(n){if(n)return n[e]})},n.nth=function(t,e){return e=(e=u(e,t))||0,n.isString(t)?t.charAt(e):t[e]},n.first=function(t){if(t)return n.nth(t,0)},n.last=function(t){var e=n.len(t);if(e)return n.nth(t,e-1)},n.asyncMap=function(n,e,r){var i,u,o=[],c=0;t(n,function(t,a){u=!0,e(t,function(t,e){if(!i){if(c++,t)return i=!0,r(t);o[a]=e,c==n.length&&(i=!0,r(null,o))}})}),u||r(null)},n.uniq=function(t){return n.uniqBy(t)},n.uniqBy=function(n,i){var u=[],o=[];return r.fn(i)||(i=null),t(n,function(n){var t=n;i&&(t=i(n)),e(o,t)||(o.push(t),u.push(n))}),u},n.flatten=function(n){var e=[];return t(n,function(n){r.arraylike(n)?t(n,function(n){e.push(n)}):e.push(n)}),e},n.union=function(){return n.uniq(n.flatten(arguments))},n.sampleSize=function(t,e){for(var r=n.toArray(t),i=r.length,u=Math.min(e||1,i),o=0;o=0?e=r:n.isObject(t)&&(e=n.keys(t).length)}return e}}},function(n,t){n.exports=function(n){var t=n.is,e=n.forIn;n.only=function(e,r){return e=e||{},t.string(r)&&(r=r.split(/ +/)),n.reduce(r,function(n,t){return null!=e[t]&&(n[t]=e[t]),n},{})},n.values=function(t){return n.map(n.keys(t),function(n){return t[n]})},n.pick=function(r,i){if(!t.fn(i))return n.pick(r,function(n,t){return t==i});var u={};return e(r,function(n,t,e){i(n,t,e)&&(u[t]=n)}),u},n.functions=function(e){return n.keys(n.pick(e,function(n){return t.fn(n)}))},n.mapKeys=function(n,t){var r={};return e(n,function(n,e,i){var u=t(n,e,i);r[u]=n}),r},n.mapObject=n.mapValues=function(n,t){var r={};return e(n,function(n,e,i){r[e]=t(n,e,i)}),r},n.get=function(t,e){if((e=u(e)).length&&n.every(e,function(n){if(null!=t)return t=t[n],!0}))return t},n.has=function(e,r){if((r=u(r)).length&&n.every(r,function(n){if(null!=e&&t.owns(e,n))return e=e[n],!0}))return!0;return!1},n.set=function(e,r,i){r=u(r);var o=e;return n.every(r,function(n,e){if(t.oof(o)){if(e+1!=r.length){if(null==(u=o[n])){var u={};~~n==n&&(u=[])}return o=o[n]=u,!0}o[n]=i}}),e},n.create=function(){function t(){}return function(e,r){return"object"!=typeof e&&(e=null),t.prototype=e,n.extend(new t,r)}}(),n.defaults=function(){var e=arguments,r=e[0],i=n.slice(e,1);return r&&n.each(i,function(e){n.mapObject(e,function(n,e){t.undef(r[e])&&(r[e]=n)})}),r},n.isMatch=function(n,t){var r=!0;return n=n||{},e(t,function(t,e){if(t!==n[e])return r=!1,!1}),r},n.toPlainObject=function(n){var t={};return e(n,function(n,e){t[e]=n}),t},n.invert=function(n){var t={};return e(n,function(n,e){t[n]=e}),t};var r=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,i=/\\(\\)?/g;function u(e){if(t.array(e))return e;var u=[];return n.toString(e).replace(r,function(n,t,e,r){var o=t||n;e&&(o=r.replace(i,"$1")),u.push(o)}),u}}},function(n,t,e){n.exports=function(n){var t=n.is,r=n.slice;function i(n){var t=new i.Cache;function e(){var e=arguments,r=e[0];if(!t.has(r)){var i=n.apply(this,e);t.set(r,i)}return t.get(r)}return e.cache=t,e}n.bind=function(e,i){if(t.string(i)){var u=e;e=u[i],i=u}if(!t.fn(e))return e;var o=r(arguments,2);return i=i||this,function(){return e.apply(i,n.flatten([o,arguments]))}},n.inherits=function(t,e){t.super_=e,t.prototype=n.create(e.prototype,{constructor:t})},n.delay=function(t,e){var r=n.slice(arguments,2);return setTimeout(function(){t.apply(this,r)},e)},n.before=function(n,t){return function(){if(n>1)return n--,t.apply(this,arguments)}},n.once=function(t){return n.before(2,t)},n.after=function(n,t){return function(){if(!(n>1))return t.apply(this,arguments);n--}},n.throttle=function(t,e,r){return e=e||0,r=n.extend({leading:!0,trailing:!0,maxWait:e},r),n.debounce(t,e,r)},n.debounce=function(t,e,r){e=e||0;var i,u=(r=n.extend({leading:!1,trailing:!0},r)).maxWait,o=0,c=0,a=n.now();function f(t,e,r){return o=n.now(),t.apply(e,r)}function s(){i&&(clearTimeout(i),i=null)}function l(){var l=!((a=n.now())-o>e||u&&a-c>u);c=a;var p=this,h=arguments;s(),l?r.trailing&&(i=n.delay(function(){f(t,p,h)},e)):f(t,p,h)}return r.leading||(o=a),l.cancel=s,l},i.Cache=e(27),n.memoize=i,n.wrap=function(n,t){return function(){var e=[n];return e.push.apply(e,arguments),t.apply(this,e)}},n.curry=function(t){var e=t.length;return function r(i){return function(){var u=i.concat(n.slice(arguments));return u.length>=e?(u.length=e,t.apply(this,u)):r(u)}}([])}}},function(n,t,e){var r=e(3).is;function i(){this.data={}}n.exports=i;var u=i.prototype;u.has=function(n){return r.owns(this.data,n)},u.get=function(n){return this.data[n]},u.set=function(n,t){this.data[n]=t},u.delete=function(n){delete this.data[n]}},function(n,t){n.exports=function(n){n.tostr=n.toString=i;var t=n.indexOf;n.split=function(n,t,e){return(n=i(n)).split(t,e)},n.capitalize=function(n){return(n=i(n)).charAt(0).toUpperCase()+n.substr(1).toLowerCase()},n.upperFirst=function(n){return(n=i(n)).charAt(0).toUpperCase()+n.substr(1)},n.lowerFirst=function(n){return(n=i(n)).charAt(0).toLowerCase()+n.substr(1)},n.camelCase=function(t){var e=(t=i(t)).split(/[^\w]|_+/);return e=n.map(e,function(t){return n.capitalize(t)}),n.lowerFirst(e.join(""))},n.startsWith=function(n,e){return 0==t(n,e)},n.endsWith=function(t,e){return(e+="")==n.slice(t,n.len(t)-n.len(e))},n.toLower=n.lower=function(n){return i(n).toLowerCase()},n.toUpper=n.upper=function(n){return i(n).toUpperCase()},n.repeat=function(t,e){return n.map(n.range(e),function(){return t}).join("")},n.padStart=function(n,t,e){return n=i(n),r(e,(t=t||0)-n.length)+n},n.padEnd=function(n,t,e){return(n=i(n))+r(e,(t=t||0)-n.length)};var e={"&":"&","<":"<",">":">",'"':""","'":"'"};function r(t,e){t=i(t)||" ";var r=Math.floor(e/t.length)+1;return n.repeat(t,r).slice(0,e)}function i(n){return n||0==n?n+"":""}n.escape=function(n){return i(n).replace(/[&<>"']/g,function(n){return e[n]||n})},n.template=function(t){var e=['with(data) {var ret = ""'];n.each(n.split(t,"<%"),function(t,r){var i=t.split("%>");if(i[1])return function(t){var r=n.first(t);if("="===r||"-"===r){var i=n.slice(t,1);"-"===r&&(i="_.escape("+i+")"),e.push("ret += "+i)}else e.push(t)}(n.trim(i[0])),u(i[1]);u(i[0])}),e.push("return ret}");var r=new Function("data",e.join("\n")),i={_:n};return function(t){return r(n.extend({},i,t))};function u(n){e.push('ret += "'+n.replace(/('|"|\\)/g,"\\$1").replace(/\r/g,"\\r").replace(/\n/g,"\\n")+'"')}}}},function(n,t){n.exports=function(n){n.sum=function(t){return n.reduce(t,function(n,t){return n+t},0)},n.max=function(t,e){var r=-1,i=-1/0;return e=e||n.identity,n.each(t,function(n,t){(n=e(n))>i&&(i=n,r=t)}),r>-1?t[r]:i},n.min=function(t,e){var r=-1,i=1/0;return e=e||n.identity,n.each(t,function(n,t){(n=e(n))-1?t[r]:i}}},function(n,t){var e,r,i=n.exports={};function u(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function c(n){if(e===setTimeout)return setTimeout(n,0);if((e===u||!e)&&setTimeout)return e=setTimeout,setTimeout(n,0);try{return e(n,0)}catch(t){try{return e.call(null,n,0)}catch(t){return e.call(this,n,0)}}}!function(){try{e="function"==typeof setTimeout?setTimeout:u}catch(n){e=u}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(n){r=o}}();var a,f=[],s=!1,l=-1;function p(){s&&a&&(s=!1,a.length?f=a.concat(f):l=-1,f.length&&h())}function h(){if(!s){var n=c(p);s=!0;for(var t=f.length;t;){for(a=f,f=[];++l1)for(var e=1;e