├── 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 | [](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 | [](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