├── .all-contributorsrc
├── .circleci
└── config.yml
├── .editorconfig
├── .eslintrc.js
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── bug_report_cn.md
│ ├── feature_request.md
│ └── feature_request_cn.md
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── commitlint.config.js
├── docs
├── .vuepress
│ ├── config.js
│ └── public
│ │ ├── logo.png
│ │ └── standard.svg
├── README.md
├── config
│ ├── README.md
│ ├── common.md
│ ├── default.md
│ ├── runtime.md
│ └── self.md
└── guide
│ ├── README.md
│ ├── export-utils.md
│ ├── form-data.md
│ ├── installation.md
│ ├── middleware.md
│ └── mock.md
├── examples
├── apis-mp
│ ├── fake-wx.js
│ ├── index.d.ts
│ ├── index.js
│ └── mock.js
└── apis-web
│ ├── fake-fn.js
│ ├── fake-get.js
│ ├── fake-post.js
│ ├── index.d.ts
│ └── index.js
├── globals.d.ts
├── jest.config.js
├── package.json
├── rollup.config.js
├── src
├── adapters
│ ├── axios.js
│ ├── index.js
│ ├── jsonp.js
│ └── wx.js
├── constants.js
├── exportUtils.js
├── index.d.ts
├── index.js
├── middlewareFns.js
└── utils
│ ├── combineUrls.js
│ ├── fp.js
│ ├── index.js
│ ├── judge.js
│ ├── logger.js
│ ├── mp.js
│ └── params.js
├── test
├── .eslintrc.js
├── __mocks__
│ └── wxMock.js
└── __tests__
│ ├── axios.test.js
│ ├── core.test.js
│ ├── custom.test.js
│ ├── exportUtils.test.js
│ ├── fn.test.js
│ ├── fp.test.js
│ ├── jsonp.test.js
│ ├── utils.test.js
│ └── wx.test.js
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "tua-api",
3 | "projectOwner": "tuateam",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md",
8 | "docs/README.md"
9 | ],
10 | "imageSize": 100,
11 | "commit": true,
12 | "commitConvention": "angular",
13 | "contributors": [
14 | {
15 | "login": "BuptStEve",
16 | "name": "StEve Young",
17 | "avatar_url": "https://avatars2.githubusercontent.com/u/11501493?v=4",
18 | "profile": "https://buptsteve.github.io",
19 | "contributions": [
20 | "code",
21 | "doc",
22 | "infra"
23 | ]
24 | },
25 | {
26 | "login": "evinma",
27 | "name": "evinma",
28 | "avatar_url": "https://avatars2.githubusercontent.com/u/16096567?v=4",
29 | "profile": "https://github.com/evinma",
30 | "contributions": [
31 | "code"
32 | ]
33 | }
34 | ],
35 | "contributorsPerLine": 7
36 | }
37 |
--------------------------------------------------------------------------------
/.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:latest
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 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'standard',
3 | parserOptions: {
4 | ecmaVersion: 10,
5 | parser: 'babel-eslint',
6 | },
7 | rules: {
8 | 'promise/param-names': 0,
9 | 'template-curly-spacing': 'off',
10 | 'comma-dangle': [2, 'always-multiline'],
11 | },
12 | globals: {
13 | wx: true,
14 | FormData: true,
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: 'bug: your bug...'
5 | labels: bug
6 | assignees: BuptStEve
7 |
8 | ---
9 |
10 | **Version**
11 | Version [e.g. 0.1.0]
12 |
13 | **Describe the bug**
14 | A clear and concise description of what the bug is. If applicable, add screenshots to help explain your problem.
15 |
16 | **To Reproduce**
17 | Steps to reproduce the behavior:
18 | 1. Go to '...'
19 | 2. Click on '....'
20 | 3. Scroll down to '....'
21 | 4. See error
22 |
23 | If it's convenient:
24 |
25 | * Add an online address to reproduce:
26 | * codepen: https://codepen.io/
27 | * jsfiddle: https://jsfiddle.net/
28 | * codesandbox: https://codesandbox.io/
29 | * Add a repository to reproduce:https://github.com/new
30 |
31 | **Expected behavior**
32 | A clear and concise description of what you expected to happen.
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report_cn.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 报告 Bug
3 | about: 发现了一个 bug!
4 | title: 'bug: your bug...'
5 | labels: bug
6 | assignees: BuptStEve
7 |
8 | ---
9 |
10 | **版本**
11 | Version [e.g. 0.1.0]
12 |
13 | **描述一下 bug**
14 | 简洁清晰地描述一下 bug。如果方便的话,添加一些截图描述你的问题。
15 |
16 | **复现 bug**
17 | 复现的步骤:
18 |
19 | 1. 首先 '...'
20 | 2. 点击了 '...'
21 | 3. 滚动到了 '...'
22 | 4. 看到了错误
23 |
24 | 如果方便的话:
25 |
26 | * 复现 bug 的在线地址:
27 | * codepen: https://codepen.io/
28 | * jsfiddle: https://jsfiddle.net/
29 | * codesandbox: https://codesandbox.io/
30 | * 复现 bug 的仓库地址:https://github.com/new
31 |
32 | **预期行为**
33 | 简洁清晰地描述一下预期行为。
34 |
35 | **附加上下文**
36 | 添加一些问题的相关上下文。
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: 'feat: your feature...'
5 | labels: enhancement
6 | assignees: BuptStEve
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request_cn.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 添加 Feature
3 | about: 新特性支持
4 | title: 'feat: your feature...'
5 | labels: enhancement
6 | assignees: BuptStEve
7 |
8 | ---
9 |
10 | **你的功能请求是否与某些问题相关?请描述**
11 | 简洁清晰地描述一下当前有什么问题。如果方便的话,添加一些截图描述你的问题。
12 |
13 | **描述您想要的解决方案**
14 | 简洁清晰地描述一下你想要的特性是怎样的。
15 |
16 | **描述你考虑过的备选方案**
17 | 简洁清晰地描述一下你考虑过的其他备选方案,可能会有什么问题。
18 |
19 | **附加上下文**
20 | 添加一些问题的相关上下文。
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | *.log
3 | coverage
4 | .DS_Store
5 | node_modules
6 | docs/.vuepress/dist/
7 |
8 | yarn.lock
9 | package-lock.json
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 StEve Young
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
tua-api
2 |
3 | 让我们优雅地调用 api~
4 |
5 |
6 | 👉完整文档地址点这里👈
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | ## `tua-api` 是什么?
28 | `tua-api` 是一个针对发起 api 请求提供辅助功能的库。采用 ES6+ 语法,并采用 jest 进行了完整的单元测试。
29 |
30 | 目前已适配:
31 |
32 | * web 端:axios, fetch-jsonp
33 | * Node 端:axios
34 | * 小程序端:wx.request
35 |
36 |
37 |
38 |
39 |
40 | ## 安装
41 | ### web 端
42 | #### 安装本体
43 |
44 | ```bash
45 | $ npm i -S tua-api
46 | # OR
47 | $ yarn add tua-api
48 | ```
49 |
50 | 然后直接导入即可
51 |
52 | ```js
53 | import TuaApi from 'tua-api'
54 | ```
55 |
56 | #### 配置武器
57 | 配置“武器”分为两种情况:
58 |
59 | * [已配置 CORS 跨域请求头](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS),或是没有跨域需求时,无需任何操作(默认采用的就是 `axios`)。
60 |
61 | * 若是用不了 CORS,那么就需要设置 `reqType: 'jsonp'` 借助 jsonp 实现跨域
62 |
63 | 但是 jsonp 只支持使用 get 的方式请求,所以如果需要发送 post 或其他方式的请求,还是需要使用 `axios`(服务端还是需要配置 CORS)。
64 |
65 | ### 小程序端
66 | #### 安装本体即可
67 |
68 | ```bash
69 | $ npm i -S tua-api
70 | # OR
71 | $ yarn add tua-api
72 | ```
73 |
74 | ```js
75 | import TuaApi from 'tua-api'
76 | ```
77 |
78 | > 小程序还用不了 npm?[@tua-mp/service](https://tuateam.github.io/tua-mp/tua-mp-service/) 了解一下?
79 |
80 | ## `tua-api` 能干什么?
81 | `tua-api` 能实现统一管理 api 配置(例如一般放在 `src/apis/` 下)。经过处理后,业务侧代码只需要这样写即可:
82 |
83 | ```js
84 | import { fooApi } from '@/apis/'
85 |
86 | fooApi
87 | .bar({ a: '1', b: '2' }) // 发起请求,a、b 是请求参数
88 | .then(console.log) // 收到响应
89 | .catch(console.error) // 处理错误
90 | ```
91 |
92 | 不仅如此,还有一些其他功能:
93 |
94 | * 参数校验
95 | * 默认参数
96 | * 中间件(koa 风格)
97 | * ...
98 |
99 | ```js
100 | // 甚至可以更进一步和 tua-storage 配合使用
101 | import TuaStorage from 'tua-storage'
102 | import { getSyncFnMapByApis } from 'tua-api'
103 |
104 | // 本地写好的各种接口配置
105 | import * as apis from '@/apis'
106 |
107 | const tuaStorage = new TuaStorage({
108 | syncFnMap: getSyncFnMapByApis(apis),
109 | })
110 |
111 | const fetchParam = {
112 | key: fooApi.bar.key,
113 | syncParams: { a: 'a', b: 'b' },
114 |
115 | // 过期时间,默认值为实例化时的值,以秒为单位
116 | expires: 10,
117 |
118 | // 是否直接调用同步函数更新数据,默认为 false
119 | // 适用于需要强制更新数据的场景,例如小程序中的下拉刷新
120 | isForceUpdate: true,
121 |
122 | // ...
123 | }
124 |
125 | tuaStorage
126 | .load(fetchParam)
127 | .then(console.log)
128 | .catch(console.error)
129 | ```
130 |
131 | ## 怎么写 `api` 配置?
132 | 拿以下 api 地址举例:
133 |
134 | * `https://example-base.com/foo/bar/something/create`
135 | * `https://example-base.com/foo/bar/something/modify`
136 | * `https://example-base.com/foo/bar/something/delete`
137 |
138 | ### 地址结构划分
139 | 以上地址,一般将其分为`3`部分:
140 |
141 | * baseUrl: `'https://example-base.com/foo/bar'`
142 | * prefix: `'something'`
143 | * pathList: `[ 'create', 'modify', 'delete' ]`
144 |
145 | ### 文件结构
146 | `api/` 一般是这样的文件结构:
147 |
148 | ```
149 | .
150 | └── apis
151 | ├── prefix-1.js
152 | ├── prefix-2.js
153 | ├── something.js // <-- 以上的 api 地址会放在这里,名字随意
154 | └── index.js
155 | ```
156 |
157 | ### 基础配置内容
158 | ```js
159 | // src/apis/something.js
160 |
161 | export default {
162 | // 接口基础地址
163 | baseUrl: 'https://example-base.com/foo/bar',
164 |
165 | // 接口的中间路径
166 | prefix: 'something',
167 |
168 | // 接口地址数组
169 | pathList: [
170 | { path: 'create' },
171 | { path: 'modify' },
172 | { path: 'delete' },
173 | ],
174 | }
175 | ```
176 |
177 | [更多配置请点击这里查看](https://tuateam.github.io/tua-api/config/common.html)
178 |
179 | ### 配置导出
180 | 最后来看一下 `apis/index.js` 该怎么写:
181 |
182 | ```js
183 | import TuaApi from 'tua-api'
184 |
185 | // 初始化
186 | const tuaApi = new TuaApi({ ... })
187 |
188 | // 使用中间件
189 | tuaApi
190 | .use(async (ctx, next) => {
191 | // 请求发起前
192 | console.log('before: ', ctx)
193 |
194 | await next()
195 |
196 | // 响应返回后
197 | console.log('after: ', ctx)
198 | })
199 | // 链式调用
200 | .use(...)
201 |
202 | export const fakeGet = tuaApi.getApi(require('./fake-get').default)
203 | export const fakePost = tuaApi.getApi(require('./fake-post').default)
204 | ```
205 |
206 | 小程序端建议使用 [@tua-mp/cli](https://tuateam.github.io/tua-mp/tua-mp-cli/) 一键生成 api。
207 |
208 | ```bash
209 | $ tuamp add api
210 | ```
211 |
212 | ### 配置的构成
213 | 在 `tua-api` 中配置分为四种:
214 |
215 | * [默认配置(调用 `new TuaApi({ ... })` 时传递的)](https://tuateam.github.io/tua-api/config/default.html)
216 | * [公共配置(和 `pathList` 同级的配置)](https://tuateam.github.io/tua-api/config/common.html)
217 | * [自身配置(`pathList` 数组中的对象上的配置)](https://tuateam.github.io/tua-api/config/self.html)
218 | * [运行配置(在实际调用接口时传递的配置)](https://tuateam.github.io/tua-api/config/runtime.html)
219 |
220 | 其中优先级自然是:
221 |
222 | `默认配置 < 公共配置 < 自身配置 < 运行配置`
223 |
224 |
225 | 👉更多配置点击这里👈
226 |
227 |
228 | ## Contributors ✨
229 |
230 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
231 |
232 |
233 |
234 |
235 |
241 |
242 |
243 |
244 |
245 |
246 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
247 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const presets = [
2 | [
3 | '@babel/preset-env',
4 | { targets: { node: 'current' } },
5 | ],
6 | ]
7 | const plugins = [
8 | [
9 | '@babel/plugin-proposal-decorators',
10 | { legacy: true },
11 | ],
12 | '@babel/plugin-proposal-object-rest-spread',
13 | ]
14 |
15 | module.exports = {
16 | env: {
17 | dev: { presets, plugins },
18 | test: { presets, plugins },
19 | production: {
20 | presets: [
21 | [
22 | '@babel/preset-env',
23 | { modules: false },
24 | ],
25 | ],
26 | plugins,
27 | },
28 | },
29 | }
30 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // https://www.npmjs.com/package/@commitlint/config-conventional
3 | extends: ['@commitlint/config-conventional'],
4 | rules: {
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | const { name } = require('../../package.json')
2 |
3 | const description = '🏗 一款可配置的通用 api 请求函数生成工具'
4 |
5 | module.exports = {
6 | base: '/' + name + '/',
7 | locales: {
8 | '/': { title: name, description },
9 | },
10 | head: [
11 | ['link', { rel: 'icon', href: '/logo.png' }],
12 | ],
13 | evergreen: true,
14 | serviceWorker: true,
15 | themeConfig: {
16 | repo: 'tuateam/tua-api',
17 | docsDir: 'docs',
18 | editLinks: true,
19 | lastUpdated: '上次更新',
20 | sidebarDepth: 2,
21 | editLinkText: '在 GitHub 上编辑此页',
22 | nav: [
23 | {
24 | text: '🌱指南',
25 | link: '/guide/',
26 | },
27 | {
28 | text: '⚙️配置',
29 | link: '/config/',
30 | },
31 | {
32 | text: '🔥生态系统',
33 | items: [
34 | { text: '📦通用本地存储', link: 'https://tuateam.github.io/tua-storage/' },
35 | { text: '🖖小程序框架', link: 'https://tuateam.github.io/tua-mp/' },
36 | { text: '🔐轻松解决滚动穿透', link: 'https://tuateam.github.io/tua-body-scroll-lock/' },
37 | ],
38 | },
39 | ],
40 | sidebar: {
41 | '/guide/': [
42 | {
43 | title: '🌱指南',
44 | collapsable: false,
45 | children: [
46 | 'installation',
47 | '',
48 | 'middleware',
49 | 'mock',
50 | 'export-utils',
51 | 'form-data',
52 | '../config/',
53 | ],
54 | },
55 | ],
56 | '/config/': [
57 | {
58 | title: '⚙️配置',
59 | collapsable: false,
60 | children: [
61 | '',
62 | 'default',
63 | 'common',
64 | 'self',
65 | 'runtime',
66 | ],
67 | },
68 | ],
69 | },
70 | serviceWorker: {
71 | updatePopup: {
72 | message: 'New content is available.',
73 | buttonText: 'Refresh',
74 | },
75 | },
76 | },
77 | }
78 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuax/tua-api/89aa0f8fc11a20542227874d5b2b3529dd186f9c/docs/.vuepress/public/logo.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/standard.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | actionText: 快速上手 →
4 | actionLink: /guide/
5 | features:
6 | - title: 支持多端
7 | details: 支持 web 端、Node 端和小程序端
8 | - title: 支持跨域
9 | details: 默认使用 axios,也支持降级为 jsonp
10 | - title: 可配置
11 | details: 可配置接口类型、请求方式、默认参数、必填参数等属性
12 | - title: 支持 mock
13 | details: 接口数据支持 mock,方便开发调试
14 | - title: 中间件
15 | details: koa 风格中间件,方便添加各种特技
16 | footer: MIT Licensed | Copyright © 2018-present StEve Young
17 | ---
18 |
19 | 让我们优雅地调用 api~
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ## Contributors ✨
46 |
47 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
48 |
49 |
50 |
51 |
52 |
58 |
59 |
60 |
61 |
62 |
63 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
64 |
--------------------------------------------------------------------------------
/docs/config/README.md:
--------------------------------------------------------------------------------
1 | # 配置说明
2 | 在 `tua-api` 中配置分为四种:
3 |
4 | * [默认配置(调用 `new TuaApi({ ... })` 时传递的)](./default.md)
5 | * [公共配置(和 `pathList` 同级的配置)](./common.md)
6 | * [自身配置(`pathList` 数组中的对象上的配置)](./self.md)
7 | * [运行配置(在实际调用接口时传递的配置)](./runtime.md)
8 |
9 | 其中优先级自然是:
10 |
11 | `默认配置 < 公共配置 < 自身配置 < 运行配置`
12 |
--------------------------------------------------------------------------------
/docs/config/common.md:
--------------------------------------------------------------------------------
1 | # 公共配置
2 | 详细地址指的是填写在 `src/apis/foobar.js` 中的一级配置。这部分的配置优先级比默认配置高,但低于各个接口的自身配置。
3 |
4 | ## type 请求类型
5 | 重命名为 `method`,`type` 属性将在 `2.0.0+` 后废弃。
6 |
7 | ## method 请求类型
8 | 所有请求类型(可忽略大小写,可选值 OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT)
9 |
10 | ```js
11 | export default {
12 | // 忽略大小写
13 | method: 'post',
14 | }
15 | ```
16 |
17 | ## host 接口基础地址
18 | 重命名为 `baseUrl`,`host` 属性将在 `2.0.0+` 后废弃。
19 |
20 | ## baseUrl 接口基础地址
21 | ```js
22 | export default {
23 | baseUrl: 'https://example-api.com/',
24 | }
25 | ```
26 |
27 | ## mock 模拟接口数据
28 | * 类型:`Object`、`Function`
29 | * 默认值:`{}`
30 |
31 | 模拟接口数据,可以直接填数据,或是填函数。函数将收到 `params` 参数对象,即最终发送给接口的数据对象。
32 |
33 | ```js
34 | export default {
35 | // 对象形式
36 | mock: { code: 0, data: 'some data' },
37 |
38 | // 函数形式
39 | mock: (params) => ({
40 | code: params.mockCode,
41 | data: params.mockData,
42 | }),
43 | }
44 | ```
45 |
46 | 详情参阅 [mock 章节](../guide/mock.md)
47 |
48 | ## prefix 接口中间地址
49 | 建议与文件同名,方便维护。
50 |
51 | ```js
52 | export default {
53 | prefix: 'foobar',
54 | }
55 | ```
56 |
57 | ## reqType 请求使用库类型
58 | 即用哪个库发起请求目前支持:jsonp、axios、wx,不填则使用默认配置中的 reqType。
59 |
60 | ```js
61 | export default {
62 | reqType: 'jsonp',
63 | }
64 | ```
65 |
66 | ## customFetch 自定义请求函数
67 | 适用于内置库覆盖不到的场景下使用,这是一个函数,将会接收接口相关配置,在函数内发起请求,返回值为一个 Promise。
68 |
69 | ```js
70 | export default {
71 | customFetch: ({ url, data, method }) => {
72 | // ...
73 | },
74 | }
75 | ```
76 |
77 | ::: warning
78 | 从优先级上来说这个参数和 `reqType` 互相替代,例如:
79 | 1. 低优先级的 `reqType` 会被高优先级的 `customFetch` 覆盖(反之亦然)
80 | 2. 若是 `reqType` 和 `customFetch` 在同级同时配置时,控制台发出告警,并且实际以 `customFetch` 为准。
81 | :::
82 |
83 | ## commonParams 公共参数
84 | 有时对于所有接口都需要添加一个公共参数。
85 |
86 | 例如在小程序端,可能需要添加 `from` 参数标记这个接口是由小程序请求了。可以这么写:
87 |
88 | ```js
89 | export default {
90 | commonParams: { from: 'miniprogram' },
91 | }
92 | ```
93 |
94 | ::: tip
95 | 后,支持函数模式
96 |
97 | ```js
98 | export default {
99 | commonParams: (params) => ({ t: Date.now(), foo: params.foo }),
100 | }
101 | ```
102 | :::
103 |
104 | ## axiosOptions 透传参数配置
105 | 由于 tua-api 是依赖于 `axios` 或是 `fetch-jsop` 来发送请求的。所以势必要提供参数透传的功能。
106 |
107 | ```js
108 | export default {
109 | // 透传 `fetch-jsonp` 需要配置的参数。例如需要传递超时时间时可添加:
110 | jsonpOptions: { timeout: 10 * 1000 },
111 |
112 | // 透传 `axios` 需要配置的参数。例如需要传递超时时间时可添加:
113 | axiosOptions: { timeout: 10 * 1000 },
114 | }
115 | ```
116 |
117 | ## jsonpOptions 透传参数配置
118 | 同上
119 |
120 | ## middleware 中间件函数数组
121 | 中间件采用的是 koa 风格,所以对于一个 api 请求,从发起请求到收到响应你都有充分的控制权。
122 |
123 | ```js
124 | export default {
125 | middleware: [ fn1, fn2, fn3 ],
126 | }
127 | ```
128 |
129 | 详情参阅:[中间件进阶](../guide/middleware.md)
130 |
131 | ## header 请求头
132 | > 注意:jsonp 的请求方式用不了哟
133 |
134 | 配置静态请求头,例如 `Content-Type` 默认为 `application/x-www-form-urlencoded`,可以这么配置进行修改。
135 |
136 | ```js
137 | export default {
138 | header: {
139 | 'Content-Type': 'application/json',
140 | },
141 | }
142 | ```
143 |
144 | ::: tip
145 | * 如果想要发送二进制数据可以参阅 [FormData](../guide/form-data.md#formdata) 章节
146 | * 如果请求头上的一些数据是异步获取到的,比如小程序端的 `cookie`。建议配置下面的 `beforeFn` 函数或是中间件,异步设置请求头。
147 | * 若当前以 `post` 的方式发送请求,且当前没有配置 `transformRequest` 时,`tua-api` 会自动调用 `JSON.stringify`,并设置 `Content-Type` 为 `'application/json'`。
148 | :::
149 |
150 | ## beforeFn 发起请求前钩子函数
151 | 在请求发起前执行的函数,因为是通过 `beforeFn().then(...)` 调用,所以注意要返回 Promise。
152 |
153 | 例如小程序端可以通过返回 `header` 传递 `cookie`,web 端使用 axios 时也可以用来修改 `header`。
154 |
155 | > 虽然 axios 配置是 `headers` 但为了和小程序端保持一致就都用 `header`
156 |
157 | ```js
158 | export default {
159 | beforeFn: () => Promise.resolve({
160 | header: { cookie: '1' },
161 | }),
162 | }
163 | ```
164 |
165 | ## afterFn 收到响应后的钩子函数
166 | 在收到响应后执行的函数,可以不用返回 `Promise`
167 |
168 | > 注意接收的参数是一个【数组】 `[res.data, ctx]`
169 |
170 | * 第一个参数是接口返回数据对象 `{ code, data, msg }`
171 | * 第二个参数是请求相关参数的对象,例如有请求的 host、type、params、fullPath、reqTime、startTime、endTime 等等
172 |
173 | 默认值如下,即返回接口数据。
174 |
175 | ```js
176 | const afterFn = ([x]) => x
177 | ```
178 |
179 | ::: warning
180 | * 该函数若是返回了数据,则业务侧将收到这个数据。所以在这里可以添加一些通用逻辑,处理返回的数据。
181 | * 该函数若是没有返回数据,业务侧也会收到 `res.data`。
182 | :::
183 |
184 | ## isShowLoading (小程序 only)
185 | 所有请求发起时是否自动展示 loading(默认为 true)。
186 |
187 | 一般来说都是需要展示 loading 的,但是有些接口轮询时如果一直展示 loading 会很奇怪。
188 |
189 | ## showLoadingFn (小程序 only)
190 | 小程序中展示 loading 的方法:
191 |
192 | * 默认值: `() => wx.showLoading({ title: '加载中' })`
193 | * 可选值: `() => wx.showLoading(YOUR_OPTIONS)`
194 | * 可选值: `wx.showNavigationBarLoading`
195 | * 或者调用你自己定义的展示 loading 方法...
196 |
197 | ## hideLoadingFn (小程序 only)
198 | 小程序中隐藏 loading 的方法:
199 |
200 | * 默认值: wx.hideLoading
201 | * 可选值: wx.hideNavigationBarLoading
202 | * 或者调用你自己定义的隐藏 loading 方法...
203 |
204 | ## useGlobalMiddleware 使用全局中间件
205 | 是否使用全局中间件,默认为 true。
206 |
207 | 适用于某些接口正好不需要调用在 `tua-api` 初始化时定义的全局中间件的情况。
208 |
209 | ## pathList 各个接口自身配置数组
210 | 这个数组中填写的是接口最后的地址。
211 |
212 | ```js
213 | export default {
214 | pathList: [
215 | {
216 | path: 'create',
217 | // 覆盖公共 middleware
218 | middleware: [],
219 | // 覆盖公共 jsonpOptions
220 | jsonpOptions: {},
221 | },
222 | {
223 | path: 'modify',
224 | // 覆盖公共 axiosOptions
225 | axiosOptions: {},
226 | },
227 | ],
228 | }
229 | ```
230 |
231 | ::: tip
232 | 在 pathList 的接口对象中填写的配置具有最高优先级!将会覆盖上一级的同名属性。
233 | :::
234 |
235 | `pathList` 中其他配置见下一节~
236 |
--------------------------------------------------------------------------------
/docs/config/default.md:
--------------------------------------------------------------------------------
1 | # 默认配置
2 | 默认配置指的就是在 `tua-api` 初始化时传递的配置
3 |
4 | ```js
5 | import TuaApi from 'tua-api'
6 |
7 | new TuaApi({
8 | baseUrl, // 即原 host
9 | reqType,
10 | middleware,
11 | customFetch,
12 | axiosOptions,
13 | jsonpOptions,
14 | defaultErrorData,
15 | })
16 | ```
17 |
18 | ## host 接口基础地址
19 | 重命名为 `baseUrl`,`host` 属性将在 `2.0.0+` 后废弃。
20 |
21 | ## baseUrl 接口基础地址
22 | 例如 `https://example.com/api/`
23 |
24 | ## reqType 请求类型
25 | 即使用哪个库发起请求目前支持:jsonp、axios、wx,不填默认使用 axios。
26 |
27 | ## middleware 中间件函数数组
28 | 【所有】请求都会调用的中间件函数数组!适合添加一些通用逻辑,例如接口上报。
29 |
30 | ## customFetch 自定义请求函数
31 | 这是一个函数,将会接收接口相关配置,在函数内发起请求,返回值为一个 Promise。
32 |
33 | ## axiosOptions 透传 axios 配置参数
34 | 【通用】的配置,会和之后的配置合并。
35 |
36 | ## jsonpOptions 透传 fetch-jsonp 配置参数
37 | 同上
38 |
39 | ## defaultErrorData 出错时的默认数据对象
40 | 默认值是 `{ code: 999, msg: '出错啦!' }`,可以根据自己的业务需要修改。
41 |
--------------------------------------------------------------------------------
/docs/config/runtime.md:
--------------------------------------------------------------------------------
1 | # 运行配置
2 | 运行配置指的是在接口实际调用时通过第二个参数传递的配置。这部分的配置优先级最高。
3 |
4 | 以下接口以导出为 `exampleApi` 为例。
5 |
6 | ```js
7 | exampleApi.foo(
8 | { ... }, // 第一个参数传接口参数
9 | { ... } // 第二个参数传接口配置
10 | )
11 | ```
12 |
13 | ## callback 回调函数参数的名称
14 | 通过 jsonp 发起请求时,在请求的 `url` 上都会有一个参数用来标识回调函数,例如 `callback=jsonp_1581908021389_16566`。
15 |
16 | `callback` 这个参数可以用来标识等号左边的值(不填则默认为 `callback`)。
17 |
18 | ```js
19 | exampleApi.foo(
20 | { ... },
21 | { callback: `cb` }
22 | )
23 | ```
24 |
25 | 最终的请求 `url` 大概是:`/foo?cb=jsonp_1581908021389_16566`。
26 |
27 | ::: tip
28 | `callback` 其实就是透传了 `fetch-jsonp` 中的 `jsonpCallback`。
29 | :::
30 |
31 | ## callbackName 回调函数名称
32 | 通过 jsonp 发起请求时,一般默认回调函数的名称都是由一些随机值构成,例如 `callback=jsonp_1581908021389_16566`
33 |
34 | 不过为了使用缓存一般需要添加 `callbackName`,但是注意重复请求时会报错(此时不设置 `callbackName` 即可)。
35 |
36 | ```js
37 | exampleApi.foo(
38 | { ... },
39 | { callbackName: `fooCallback` }
40 | )
41 | ```
42 |
43 | 最终的请求 `url` 大概是:`/foo?callback=fooCallback`。
44 |
45 | ::: tip
46 | `callbackName` 其实就是透传了 `fetch-jsonp` 中的 `jsonpCallbackFunction`。
47 | :::
48 |
49 | ## 其他参数
50 | 公共配置一节中的所有参数(除了 `pathList` 外),以及自身配置一节中的所有参数均有效,且优先级最高。
51 |
52 | * 详情参阅[公共配置](./common.md)
53 | * 详情参阅[自身配置](./self.md)
54 |
--------------------------------------------------------------------------------
/docs/config/self.md:
--------------------------------------------------------------------------------
1 | # 自身配置
2 | 自身配置指的是填写在 `pathList` 中的配置。这部分的配置优先级比公共配置高,但低于各个接口的运行配置。
3 |
4 | 以下接口以导出为 `exampleApi` 为例。
5 |
6 | ## path 接口地址
7 | ```js
8 | export default {
9 | pathList: [
10 | {
11 | path: 'foo-bar',
12 | },
13 | ],
14 | }
15 | ```
16 |
17 | 即接口地址的最后部分。默认这样调用
18 |
19 | ```js
20 | exampleApi['foo-bar']({ ... })
21 | ```
22 |
23 | ## name 接口名称(可省略)
24 | ```js
25 | export default {
26 | pathList: [
27 | {
28 | path: 'foo-bar',
29 | name: 'fooBar',
30 | },
31 | ],
32 | }
33 | ```
34 |
35 | 有时接口地址较长或不方便直接调用,可以添加 `name` 配置重命名接口,这样就可以这样调用
36 |
37 | ```js
38 | exampleApi.fooBar({ ... })
39 | ```
40 |
41 | ## params 接口参数
42 |
43 | ```js
44 | export default {
45 | pathList: [
46 | {
47 | path: 'create',
48 | // 数组形式(不推荐使用)
49 | params: [ 'a', 'b' ],
50 | },
51 | {
52 | path: 'modify',
53 | // 对象形式(推荐使用)
54 | params: {
55 | // 默认参数
56 | a: '1',
57 | // 表示该参数在调用时必须传,以下两种写法都行
58 | b: { required: true },
59 | c: { isRequired: true },
60 | },
61 | },
62 | ],
63 | }
64 | ```
65 |
66 | ::: tip
67 | 后,支持函数模式
68 |
69 | ```js
70 | export default {
71 | pathList: [
72 | {
73 | ...
74 | params: (params) => ({
75 | t: Date.now(),
76 | foo: params.foo,
77 | }),
78 | },
79 | ],
80 | }
81 | ```
82 | :::
83 |
84 | ## commonParams 覆盖公共参数
85 | 有时某个接口正好不需要上一级中 `commonParams` 的参数。那么可以传递 `null` 覆盖上一级中的 `commonParams`。
86 |
87 | ## 其他参数
88 | 上一节中的所有参数(除了 `pathList` 外)均有效。
89 |
90 | 详情参阅上一节 [公共配置](./common.md)
91 |
--------------------------------------------------------------------------------
/docs/guide/README.md:
--------------------------------------------------------------------------------
1 | # 介绍
2 | ## `tua-api` 是什么?
3 | `tua-api` 是一个针对发起 api 请求提供辅助功能的库。采用 ES6+ 语法,并采用 jest 进行了完整的单元测试。
4 |
5 | 目前已适配:
6 |
7 | * web 端:axios, fetch-jsonp
8 | * Node 端:axios
9 | * 小程序端:wx.request
10 |
11 | ## `tua-api` 能干什么?
12 | `tua-api` 能实现统一管理 api 配置(例如一般放在 `src/apis/` 下)。经过处理后,业务侧代码只需要这样写即可:
13 |
14 | ```js
15 | import { fooApi } from '@/apis/'
16 |
17 | fooApi
18 | .bar({ a: '1', b: '2' }) // 发起请求,a、b 是请求参数
19 | .then(console.log) // 收到响应
20 | .catch(console.error) // 处理错误
21 | ```
22 |
23 | 不仅如此,还有一些其他功能:
24 |
25 | * 参数校验
26 | * 默认参数
27 | * 中间件(koa 风格)
28 | * ...
29 |
30 | ```js
31 | // 甚至可以更进一步和 tua-storage 配合使用
32 | import TuaStorage from 'tua-storage'
33 | import { getSyncFnMapByApis } from 'tua-api'
34 |
35 | // 本地写好的各种接口配置
36 | import * as apis from '@/apis'
37 |
38 | const tuaStorage = new TuaStorage({
39 | syncFnMap: getSyncFnMapByApis(apis),
40 | })
41 |
42 | const fetchParam = {
43 | key: fooApi.bar.key,
44 | syncParams: { a: 'a', b: 'b' },
45 |
46 | // 过期时间,默认值为实例化时的值,以秒为单位
47 | expires: 10,
48 |
49 | // 是否直接调用同步函数更新数据,默认为 false
50 | // 适用于需要强制更新数据的场景,例如小程序中的下拉刷新
51 | isForceUpdate: true,
52 |
53 | // ...
54 | }
55 |
56 | tuaStorage
57 | .load(fetchParam)
58 | .then(console.log)
59 | .catch(console.error)
60 | ```
61 |
62 | ## 怎么写 `api` 配置?
63 | 拿以下 api 地址举例:
64 |
65 | * `https://example-base.com/foo/bar/something/create`
66 | * `https://example-base.com/foo/bar/something/modify`
67 | * `https://example-base.com/foo/bar/something/delete`
68 |
69 | ### 地址结构划分
70 | 以上地址,一般将其分为`3`部分:
71 |
72 | * baseUrl: `'https://example-base.com/foo/bar'`
73 | * prefix: `'something'`
74 | * pathList: `[ 'create', 'modify', 'delete' ]`
75 |
76 | ### 文件结构
77 | `api/` 一般是这样的文件结构:
78 |
79 | ```
80 | .
81 | └── apis
82 | ├── prefix-1.js
83 | ├── prefix-2.js
84 | ├── something.js // <-- 以上的 api 地址会放在这里,名字随意
85 | └── index.js
86 | ```
87 |
88 | ### 基础配置内容
89 | ```js
90 | // src/apis/something.js
91 |
92 | export default {
93 | // 接口基础地址
94 | baseUrl: 'https://example-base.com/foo/bar',
95 |
96 | // 接口的中间路径
97 | prefix: 'something',
98 |
99 | // 接口地址数组
100 | pathList: [
101 | { path: 'create' },
102 | { path: 'modify' },
103 | { path: 'delete' },
104 | ],
105 | }
106 | ```
107 |
108 | [更多配置请点击这里查看](../config/common.md)
109 |
110 | ### 配置导出
111 | 最后来看一下 `apis/index.js` 该怎么写:
112 |
113 | ```js
114 | import TuaApi from 'tua-api'
115 |
116 | // 初始化
117 | const tuaApi = new TuaApi({ ... })
118 |
119 | // 使用中间件
120 | tuaApi
121 | .use(async (ctx, next) => {
122 | // 请求发起前
123 | console.log('before: ', ctx)
124 |
125 | await next()
126 |
127 | // 响应返回后
128 | console.log('after: ', ctx)
129 | })
130 | // 链式调用
131 | .use(...)
132 |
133 | export const fakeGet = tuaApi.getApi(require('./fake-get').default)
134 | export const fakePost = tuaApi.getApi(require('./fake-post').default)
135 | ```
136 |
137 | ::: tip
138 | 小程序端建议使用 [@tua-mp/cli](https://tuateam.github.io/tua-mp/tua-mp-cli/) 一键生成 api。
139 |
140 | ```bash
141 | $ tuamp add api
142 | ```
143 | :::
144 |
145 | [配置的详细说明点这里](../config/)
146 |
--------------------------------------------------------------------------------
/docs/guide/export-utils.md:
--------------------------------------------------------------------------------
1 | # 辅助函数
2 | ## getSyncFnMapByApis
3 | 将所有的 api 对象拍平成一个 Map,与 `tua-storage` 配合使用可以将各个发起 `api` 的函数的 `key` 与其自身绑定。
4 |
5 | ## getPreFetchFnKeysBySyncFnMap
6 | 过滤出有默认参数的接口(接口参数非数组,且不含有 isRequired)。
7 |
8 | 适用于 node 端发起请求预取数据的场景。
9 |
--------------------------------------------------------------------------------
/docs/guide/form-data.md:
--------------------------------------------------------------------------------
1 | # FormData
2 | ## 发送二进制数据
3 | 日常使用中,除了简单的字符串参数以外,有时也会遇到需要发送二进制数据的场景,例如上传文件。
4 |
5 | 在旧版的 `tua-api` 中若是遇到这种请求,也能发送但比较繁琐。
6 |
7 | ```js
8 | const formData = new FormData()
9 |
10 | imgUploadApi.userUpload(null, {
11 | reqFnParams: { reqParams: formData },
12 | axiosOptions: { transformRequest: null },
13 | })
14 | ```
15 |
16 | 如上例所示,借助第二个参数[运行时配置](../config/runtime.md)设置了:数据、 `transformRequest`。
17 |
18 | 而在新版本的 `tua-api` 中,只要这么调用即可:
19 |
20 | ```js
21 | const formData = new FormData()
22 |
23 | imgUploadApi.userUpload(formData)
24 | ```
25 |
26 | 实现原理是 `tua-api` 在底层判断出接收的接口参数是 `FormData` 类型的数据,自动设置了 `transformRequest`。
27 |
28 | > 小程序端暂时建议使用原生的 `wx.uploadFile`。
29 |
--------------------------------------------------------------------------------
/docs/guide/installation.md:
--------------------------------------------------------------------------------
1 | # 安装
2 | ## web 端
3 | ### 安装本体
4 |
5 | ```bash
6 | $ npm i -S tua-api
7 | # OR
8 | $ yarn add tua-api
9 | ```
10 |
11 | 然后直接导入即可
12 |
13 | ```js
14 | import TuaApi from 'tua-api'
15 | ```
16 |
17 | #### 配置武器
18 | 配置“武器”分为两种情况:
19 |
20 | * [已配置 CORS 跨域请求头](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS),或是没有跨域需求时,无需任何操作(默认采用的就是 `axios`)。
21 |
22 | * 若是用不了 CORS,那么就需要设置 `reqType: 'jsonp'` 借助 jsonp 实现跨域
23 |
24 | 但是 jsonp 只支持使用 get 的方式请求,所以如果需要发送 post 或其他方式的请求,还是需要使用 `axios`(服务端还是需要配置 CORS)。
25 |
26 | ::: tip
27 | 不推荐使用 jsonp 的方式,有以下几个原因:
28 |
29 | 1.频繁报错,并且报错信息比较含糊
30 |
31 | 2.为了使用缓存一般添加 callbackName,但是重复请求会报错
32 | :::
33 |
34 | ## 小程序端
35 | ### 安装本体即可
36 |
37 | ```bash
38 | $ npm i -S tua-api
39 | # OR
40 | $ yarn add tua-api
41 | ```
42 |
43 | ```js
44 | import TuaApi from 'tua-api'
45 | ```
46 |
47 | ::: tip
48 | 小程序还用不了 npm?[@tua-mp/service](https://tuateam.github.io/tua-mp/tua-mp-service/) 了解一下?
49 | :::
50 |
--------------------------------------------------------------------------------
/docs/guide/middleware.md:
--------------------------------------------------------------------------------
1 | # 中间件进阶
2 | 在这一节中聊聊中间件该怎么用、注意事项、参数含义等等。
3 |
4 | ```js
5 | export default {
6 | middleware: [ fn1, fn2, fn3 ],
7 | }
8 | ```
9 |
10 | ## 中间件执行顺序
11 | koa 中间件的执行顺序和 redux 的正好相反,例如以上写法会以以下顺序执行:
12 |
13 | `请求参数 -> fn1 -> fn2 -> fn3 -> 响应数据 -> fn3 -> fn2 -> fn1`
14 |
15 | ## 中间件写法
16 |
17 | * 普通函数:注意一定要 `return next()` 否则 `Promise` 链就断了!
18 | * async 函数:注意一定要 `await next()`!
19 |
20 | ```js
21 | // 普通函数,注意一定要 return next()
22 | function (ctx, next) {
23 | ctx.req // 请求的各种配置
24 | ctx.res // 响应,但这时还未发起请求,所以是 undefined!
25 | ctx.startTime // 发起请求的时间
26 |
27 | // 传递控制权给下一个中间件
28 | return next().then(() => {
29 | // 注意这里才有响应!
30 | ctx.res // 响应对象
31 | ctx.res.data // 响应格式化后的数据
32 | ctx.res.rawData // 响应的原始数据
33 | ctx.reqTime // 请求花费的时间
34 | ctx.endTime // 收到响应的时间
35 | })
36 | }
37 |
38 | // async/await
39 | async function (ctx, next) {
40 | ctx.req // 请求的各种配置
41 |
42 | // 传递控制权给下一个中间件
43 | await next()
44 |
45 | // 注意这里才有响应!
46 | ctx.res // 响应对象
47 | }
48 | ```
49 |
50 | ## 中间件参数
51 |
52 | 以下是挂在 ctx 下的各种属性,业务侧的中间件可以改写其中某些属性达到在请求发起前,以及在收到响应后进行某些操作。
53 |
54 | | 已使用的属性名 | 含义和作用 |
55 | | --- | --- |
56 | | req | 请求 |
57 | | req.host | 接口基础地址 |
58 | | req.baseUrl | 接口基础地址 |
59 | | req.mock | 模拟的响应数据或是生成数据的函数 |
60 | | req.type | 接口请求类型 get/post... |
61 | | req.method | 接口请求类型 get/post... |
62 | | req.path | 接口结尾路径 |
63 | | req.prefix | 接口前缀 |
64 | | req.reqType | 使用什么工具发(axios/jsonp/wx) |
65 | | req.reqParams | 已添加默认参数的请求参数 |
66 | | req.callbackName | 使用 jsonp 时的回调函数名 |
67 | | req.axiosOptions | 透传 axios 配置参数 |
68 | | req.jsonpOptions | 透传 fetch-jsonp 配置参数|
69 | | req.reqFnParams | 发起请求时的参数对象(上面那些参数都会被放进来作为属性) |
70 | | --- | --- |
71 | | res | 响应 |
72 | | res.data | 响应格式化后的数据 |
73 | | res.rawData | 响应的原始数据 |
74 | | res.error | 错误对象(可以取 stack 和 message) |
75 | | res.* | [透传 axios 的配置](https://github.com/axios/axios#response-schema) |
76 | | --- | --- |
77 | | reqTime | 请求花费的时间 |
78 | | startTime | 请求开始时的时间戳 |
79 | | endTime | 收到响应时的时间戳 |
80 |
--------------------------------------------------------------------------------
/docs/guide/mock.md:
--------------------------------------------------------------------------------
1 | # 数据 mock
2 | ## 静态配置
3 | 即将 mock 数据直接填在该接口的配置中。
4 |
5 | ### 简单对象
6 | 简单粗暴,填数据就完事儿了~
7 |
8 | ```js
9 | {
10 | pathList: [
11 | // 以 foo 接口为例
12 | {
13 | path: 'foo',
14 |
15 | // 对象形式
16 | mock: { code: 0, data: 'some data' },
17 | },
18 | ],
19 | }
20 | ```
21 |
22 | ### mock 函数
23 | 使用函数形式,用法上会更灵活一些。
24 |
25 | ```js
26 | {
27 | pathList: [
28 | // 以 foo 接口为例
29 | {
30 | path: 'foo',
31 |
32 | // 函数形式
33 | mock: (params) => ({
34 | code: params.mockCode,
35 | data: params.mockData,
36 | }),
37 | },
38 | ],
39 | }
40 | ```
41 |
42 | ::: tip
43 | `params` 即最终传入接口的参数对象。
44 | :::
45 |
46 | ```js
47 | import { exampleApi } from '@/apis/'
48 |
49 | // 填写 mock 数据
50 | const mockCode = 0
51 | const mockData = { foo: 'bar' }
52 |
53 | // 请求将收到 mock 数据
54 | exampleApi.foo({ mockCode, mockData })
55 | .then(({ code, data }) => {
56 | console.log(code, data) // 0 {foo: "bar"}
57 | })
58 | ```
59 |
60 | ### 多接口公共 mock
61 | mock 属性不仅可以填在各个接口处,也可以将其放在上一级,mock 当前配置中的所有接口。
62 |
63 | ```js
64 | {
65 | // 公共 mock
66 | mock: ({ __mockData__ }) => __mockData__,
67 |
68 | pathList: [
69 | // 自身的 mock 配置优先级更高
70 | { path: 'foo', mock: { code: 0 } },
71 |
72 | // 没填自身 mock,则默认使用公共 mock
73 | { path: 'bar' },
74 |
75 | // 禁用 mock
76 | { path: 'null', mock: null },
77 | ],
78 | }
79 | ```
80 |
81 | ```js
82 | import { exampleApi } from '@/apis/'
83 |
84 | const __mockData__ = { code: 123 }
85 |
86 | // 使用自己定义 mock 数据
87 | exampleApi.foo({ __mockData__ })
88 | .then(({ code }) => {
89 | console.log(code) // 0
90 | })
91 |
92 | // 使用公共的 mock 数据
93 | exampleApi.bar({ __mockData__ })
94 | .then(({ code }) => {
95 | console.log(code) // 123
96 | })
97 | ```
98 |
99 | 更多配置优先级内容请参阅[配置说明](../config/)部分。
100 |
101 | ## 动态配置
102 | 即为每个导出的 `api` 函数添加 `mock` 属性,在业务侧用以下方式调用。
103 |
104 | ```js
105 | import { exampleApi } from '@/apis/'
106 |
107 | // 填写 mock 数据
108 | exampleApi.foo.mock = {
109 | code: 0,
110 | data: { foo: 'bar' },
111 | }
112 |
113 | // 同样支持 mock 函数
114 | exampleApi.foo.mock = () => ({
115 | code: 0,
116 | data: { foo: 'bar' },
117 | })
118 |
119 | // 请求将收到 mock 数据
120 | exampleApi.foo().then(({ code, data }) => {
121 | console.log(code, data) // 0 {foo: "bar"}
122 | })
123 | ```
124 |
125 | ## 同时配置
126 | ### 优先级
127 | 若是同时配置了静态和动态 mock,动态配置的 mock 数据优先级更高。
128 |
129 | ::: tip
130 | 优先级:动态 > 静态
131 | :::
132 |
133 | * 接口配置
134 | ```js
135 | {
136 | pathList: [
137 | {
138 | path: 'foo',
139 | mock: (params) => ({ code: params.mockCode }),
140 | },
141 | ],
142 | }
143 | ```
144 |
145 | * 业务侧
146 | ```js
147 | import { exampleApi } from '@/apis/'
148 |
149 | // 动态配置的数据将覆盖静态配置的数据
150 | exampleApi.foo.mock = { code: 1 }
151 |
152 | exampleApi.foo({ mockCode: 0 })
153 | .then(({ code }) => {
154 | console.log(code) // 1
155 | })
156 | ```
157 |
158 | ### 关闭 mock
159 | 可以通过以下代码实现关闭 mock 功能。
160 |
161 | ```js
162 | import { exampleApi } from '@/apis/'
163 |
164 | // 关闭 mock
165 | exampleApi.foo.mock = null
166 |
167 | // 即使传递 mock 数据也不起作用
168 | exampleApi.foo({ mockCode: 404 })
169 | .then(({ code }) => {
170 | console.log(code) // 实际接口的返回值
171 | })
172 | ```
173 |
174 | ::: tip
175 | 其实动态配置 `exampleApi.foo.mock` 的默认值就是静态配置的值,而在 `tua-api` 底层读取的就是 `exampleApi.foo.mock`。
176 |
177 | 所以自然动态配置的优先级更高,并且赋值为 `null` 即可关闭 mock。
178 | :::
179 |
--------------------------------------------------------------------------------
/examples/apis-mp/fake-wx.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 该参数表示请求的公用服务器地址。
3 | baseUrl: 'http://example-base.com/',
4 |
5 | // 该参数表示请求的中间路径,建议与文件同名,以便后期维护。
6 | prefix: 'fake-wx',
7 |
8 | // 所有请求类型
9 | /** @type { import('../../src/').Method } */
10 | type: ('post'),
11 |
12 | // 所有请求都需要携带的参数,例如小程序中的所有接口都要携带以下参数 `from=miniprogram`
13 | commonParams: { from: 'miniprogram' },
14 |
15 | // 是否使用在 index.js 中定义的全局中间件,默认为 true
16 | useGlobalMiddleware: false,
17 |
18 | // 所有请求发起时是否自动展示 loading(默认为 true)
19 | // isShowLoading: true,
20 |
21 | // 中间件函数数组
22 | // middleware: [],
23 |
24 | // 接口地址数组
25 | pathList: [
26 | /**
27 | * fail
28 | */
29 | {
30 | path: 'fail',
31 | /**
32 | * @returns {Promise}
33 | */
34 | beforeFn: () => Promise.resolve({
35 | header: { cookie: '123' },
36 | }),
37 | useGlobalMiddleware: true,
38 | },
39 | /**
40 | * anotherFail
41 | */
42 | {
43 | path: 'fail',
44 | name: 'anotherFail',
45 | },
46 | /**
47 | * array-data
48 | */
49 | {
50 | name: 'arrayData',
51 | path: 'array-data',
52 | /** @type { import('../../src/').Method } */
53 | type: ('get'),
54 | params: ['param1', 'param2'],
55 | },
56 | /**
57 | * object-data
58 | */
59 | {
60 | name: 'objectData',
61 | path: 'object-data',
62 | params: {
63 | param1: 1217,
64 | param2: 'steve',
65 | param3: { isRequired: true },
66 | },
67 | },
68 | /**
69 | * no-beforeFn
70 | */
71 | {
72 | name: 'noBeforeFn',
73 | path: 'no-beforeFn',
74 | commonParams: null,
75 | },
76 | /**
77 | * hide-loading
78 | */
79 | {
80 | name: 'hideLoading',
81 | path: 'hide-loading',
82 | // 这个接口不需要展示 loading
83 | isShowLoading: false,
84 | },
85 | /**
86 | * type-get
87 | */
88 | {
89 | name: 'typeGet',
90 | path: 'type-get',
91 | // 这个接口单独配置类型
92 | /** @type { import('../../src/').Method } */
93 | type: ('get'),
94 | },
95 | /**
96 | * unknown-type
97 | */
98 | {
99 | name: 'unknownType',
100 | path: 'unknown-type',
101 | // 这个接口单独配置类型
102 | /** @type { import('../../src/').Method } */
103 | type: ('foo'),
104 | },
105 | /**
106 | * nav-loading
107 | */
108 | {
109 | name: 'navLoading',
110 | path: 'nav-loading',
111 | showLoadingFn: wx.showNavigationBarLoading,
112 | hideLoadingFn: wx.hideNavigationBarLoading,
113 | },
114 | ],
115 | }
116 |
--------------------------------------------------------------------------------
/examples/apis-mp/index.d.ts:
--------------------------------------------------------------------------------
1 | // default response result
2 | interface Result { code: number, data: any, msg?: string }
3 | interface ReqFn {
4 | key: string
5 | mock: any
6 | params: object | string[]
7 | }
8 | interface RuntimeOptions {
9 | // for jsonp
10 | callbackName?: string
11 | [key: string]: any
12 | }
13 | interface ReqFnWithAnyParams extends ReqFn {
14 | (params?: any, options?: RuntimeOptions): Promise
15 | }
16 |
17 | export const mockApi: {
18 | 'foo': ReqFnWithAnyParams
19 | 'bar': ReqFnWithAnyParams
20 | 'null': ReqFnWithAnyParams
21 | }
22 |
23 | export const fakeWxApi: {
24 | 'fail': ReqFnWithAnyParams
25 | 'typeGet': ReqFnWithAnyParams
26 | 'noBeforeFn': ReqFnWithAnyParams
27 | 'navLoading': ReqFnWithAnyParams
28 | 'anotherFail': ReqFnWithAnyParams
29 | 'hideLoading': ReqFnWithAnyParams
30 | 'unknownType': ReqFnWithAnyParams
31 | 'arrayData': ReqFn & {
32 | (
33 | params: { param1?: any, param2?: any },
34 | options?: RuntimeOptions
35 | ): Promise
36 | }
37 | 'objectData': ReqFn & {
38 | (
39 | params: { param1?: any, param2?: any, param3: any },
40 | options?: RuntimeOptions
41 | ): Promise
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/apis-mp/index.js:
--------------------------------------------------------------------------------
1 | import TuaApi from '../../src'
2 |
3 | const tuaApi = new TuaApi()
4 |
5 | // 使用中间件
6 | tuaApi.use(async (ctx, next) => {
7 | // 请求发起前
8 | // console.log('before: ', ctx)
9 |
10 | await next()
11 |
12 | // 响应返回后
13 | // console.log('after: ', ctx)
14 | })
15 |
16 | export const mockApi = tuaApi.getApi(require('./mock').default)
17 | export const fakeWxApi = tuaApi.getApi(require('./fake-wx').default)
18 |
--------------------------------------------------------------------------------
/examples/apis-mp/mock.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 该参数表示请求的公用服务器地址。
3 | host: 'http://example-base.com/',
4 |
5 | // 该参数表示请求的中间路径,建议与文件同名,以便后期维护。
6 | prefix: 'mock',
7 |
8 | // 所有请求类型
9 | /** @type { import('../../src/').Method } */
10 | type: ('get'),
11 |
12 | // 公共 mock
13 | mock: ({ __mockData__ }) => __mockData__,
14 |
15 | pathList: [
16 | // 自身的 mock 配置优先级更高
17 | { path: 'foo', mock: { code: 500 } },
18 |
19 | // 没填自身 mock,则默认使用公共 mock
20 | { path: 'bar' },
21 |
22 | // 禁用 mock
23 | { path: 'null', mock: null },
24 | ],
25 | }
26 |
--------------------------------------------------------------------------------
/examples/apis-web/fake-fn.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 该参数表示请求的公用服务器地址。
3 | baseUrl: 'http://example-base.com/',
4 |
5 | // 请求的中间路径,建议与文件同名,以便后期维护。
6 | prefix: 'fake-fn',
7 |
8 | // 所有请求都需要携带的参数
9 | commonParams: (args) => ({
10 | ...args,
11 | c: Math.random(),
12 | }),
13 |
14 | reqType: 'axios',
15 |
16 | // 接口地址数组
17 | pathList: [
18 | /**
19 | * fn-params
20 | */
21 | {
22 | name: 'fp',
23 | path: 'fn-params',
24 | method: 'post',
25 | params: ({ param1, param2 }) => ({
26 | t: Math.random(),
27 | p1: param1,
28 | p2: param2,
29 | }),
30 | },
31 | ],
32 | }
33 |
--------------------------------------------------------------------------------
/examples/apis-web/fake-get.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 请求的公用服务器地址。
3 | // baseUrl: 'http://example-base.com/',
4 |
5 | // 请求的中间路径,建议与文件同名,以便后期维护。
6 | prefix: 'fake-get',
7 |
8 | // 所有请求都需要携带的参数
9 | commonParams: null,
10 |
11 | // 透传 `fetch-jsonp` 需要配置的参数。例如需要传递超时时间时可添加:
12 | jsonpOptions: {
13 | timeout: 10 * 1000,
14 | jsonpCallback: 'cb',
15 | jsonpCallbackFunction: 'cbName',
16 | },
17 |
18 | // 透传 `axios` 需要配置的参数。例如需要传递超时时间时可添加:
19 | axiosOptions: { timeout: 10 * 1000 },
20 |
21 | // 是否使用在 index.js 中定义的全局中间件,默认为 true
22 | useGlobalMiddleware: false,
23 |
24 | // 中间件函数数组
25 | middleware: [
26 | // (ctx, next) => {
27 | // // 请求发起前
28 | // console.log('before: ', ctx)
29 |
30 | // return next().then(() => {
31 | // // 响应返回后
32 | // console.log('after: ', ctx)
33 | // })
34 | // },
35 | ],
36 |
37 | // 接口地址数组
38 | pathList: [
39 | /**
40 | * empty-array-params
41 | */
42 | {
43 | path: 'empty-array-params',
44 | },
45 | /**
46 | * array-params
47 | */
48 | {
49 | name: 'ap',
50 | path: 'array-params',
51 | params: ['param1', 'param2'],
52 | // 在这里定义将覆盖公共中间件
53 | middleware: [],
54 | },
55 | /**
56 | * object-params
57 | */
58 | {
59 | name: 'op',
60 | path: 'object-params',
61 | /** @type { import('../../src/').ReqType } */
62 | reqType: (''),
63 | params: {
64 | param1: 1217,
65 | param2: 'steve',
66 | // isRequired 或者 required 都行
67 | param3: { required: true },
68 | },
69 | },
70 | /**
71 | * async-common-params
72 | */
73 | {
74 | name: 'acp',
75 | path: 'async-common-params',
76 | params: [],
77 | /**
78 | * 在这里返回的 params 会和请求的 params 合并
79 | * @returns {Promise}
80 | */
81 | beforeFn: () => Promise.resolve({
82 | params: { asyncCp: 'asyncCp' },
83 | }),
84 | },
85 | /**
86 | * req-type-axios
87 | */
88 | {
89 | name: 'rta',
90 | path: 'req-type-axios',
91 | // 用哪个包发起请求目前支持:jsonp、axios
92 | // 如果不指定默认对于 get 请求使用 fetch-jsonp,post 请求使用 axios
93 | /** @type { import('../../src/').ReqType } */
94 | reqType: ('axios'),
95 | /**
96 | * 在这里返回的 params 会和请求的 params 合并
97 | * @returns {Promise}
98 | */
99 | beforeFn: () => Promise.resolve({
100 | params: { asyncCp: 'asyncCp' },
101 | }),
102 | },
103 | /**
104 | * invalid-req-type
105 | */
106 | {
107 | name: 'irt',
108 | path: 'invalid-req-type',
109 | /** @type { import('../../src/').ReqType } */
110 | reqType: ('foobar'),
111 | },
112 | /**
113 | * afterFn-data
114 | */
115 | {
116 | name: 'afterData',
117 | path: 'afterFn-data',
118 | afterFn: ([data]) => ({ ...data, afterData: 'afterData' }),
119 | },
120 | /**
121 | * no-afterFn-data
122 | */
123 | {
124 | name: 'noAfterData',
125 | path: 'no-afterFn-data',
126 | afterFn: () => {},
127 | },
128 | /**
129 | * mock-object-data
130 | */
131 | {
132 | name: 'mockObjectData',
133 | path: 'mock-object-data',
134 | mock: { code: 404, data: {} },
135 | },
136 | /**
137 | * mock-function-data
138 | */
139 | {
140 | name: 'mockFnData',
141 | path: 'mock-function-data',
142 | /** @type { import('../../src/').ReqType } */
143 | reqType: ('axios'),
144 | mock: ({ mockCode }) => ({ code: mockCode, data: {} }),
145 | },
146 | /**
147 | * beforeFnCookie
148 | */
149 | {
150 | name: 'beforeFnCookie',
151 | path: 'beforeFn-cookie',
152 | /**
153 | * @returns {Promise}
154 | */
155 | beforeFn: () => Promise.resolve({
156 | header: { cookie: '123' },
157 | }),
158 | /** @type { import('../../src/').ReqType } */
159 | reqType: ('axios'),
160 | },
161 | /**
162 | * jsonp-options
163 | */
164 | {
165 | name: 'jsonpOptions',
166 | path: 'jsonp-options',
167 | },
168 | ],
169 | }
170 |
--------------------------------------------------------------------------------
/examples/apis-web/fake-post.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 该参数表示请求的公用服务器地址。
3 | baseUrl: 'http://example-base.com/',
4 |
5 | // 该参数表示请求的中间路径,建议与文件同名,以便后期维护。
6 | prefix: 'fake-post',
7 |
8 | /** @type { import('../../src/').Method } */
9 | method: ('post'),
10 |
11 | // 所有请求都需要携带的参数
12 | commonParams: { common: 'params' },
13 |
14 | // 中间件函数数组
15 | middleware: [],
16 |
17 | // 接口地址数组
18 | pathList: [
19 | /**
20 | * empty-array-params
21 | */
22 | { name: 'eap', path: 'empty-array-params' },
23 | /**
24 | * array-params
25 | */
26 | {
27 | name: 'ap',
28 | path: 'array-params',
29 | /** @type { import('../../src/').ReqType } */
30 | reqType: ('axios'),
31 | params: ['param1', 'param2'],
32 | },
33 | /**
34 | * array-params with new baseUrl
35 | */
36 | {
37 | name: 'hap',
38 | path: 'array-params',
39 | /** @type { import('../../src/').ReqType } */
40 | reqType: ('axios'),
41 | middleware: [
42 | async (ctx, next) => {
43 | ctx.req.baseUrl = 'http://custom-baseUrl.com/'
44 | await next()
45 | },
46 | ],
47 | },
48 | /**
49 | * object-params
50 | */
51 | {
52 | name: 'op',
53 | path: 'object-params',
54 | params: {
55 | param1: 1217,
56 | param2: 'steve',
57 | param3: { isRequired: true },
58 | },
59 | },
60 | /**
61 | * own-baseUrl
62 | */
63 | {
64 | name: 'oh',
65 | path: 'own-baseUrl',
66 | baseUrl: 'http://example-test.com/',
67 | params: {},
68 | // 表示这个接口不需要传递 commonParams
69 | commonParams: null,
70 | },
71 | /**
72 | * custom-transformRequest
73 | */
74 | {
75 | name: 'ct',
76 | path: 'custom-transformRequest',
77 | axiosOptions: {
78 | transformRequest: () => 'ct',
79 | },
80 | },
81 | /**
82 | * application/json
83 | */
84 | { name: 'pj', path: 'post-json' },
85 | /**
86 | * raw-data
87 | */
88 | {
89 | name: 'rd',
90 | path: 'raw-data',
91 | afterFn: ([, ctx]) => ctx.res.rawData,
92 | },
93 | ],
94 | }
95 |
--------------------------------------------------------------------------------
/examples/apis-web/index.d.ts:
--------------------------------------------------------------------------------
1 | // default response result
2 | interface Result { code: number, data: any, msg?: string }
3 | interface ReqFn {
4 | key: string
5 | mock: any
6 | params: object | string[]
7 | }
8 | interface RuntimeOptions {
9 | // for jsonp
10 | callbackName?: string
11 | [key: string]: any
12 | }
13 | interface ReqFnWithAnyParams extends ReqFn {
14 | (params?: any, options?: RuntimeOptions): Promise
15 | }
16 |
17 | export const fakeGetApi: {
18 | 'acp': ReqFnWithAnyParams
19 | 'rta': ReqFnWithAnyParams
20 | 'irt': ReqFnWithAnyParams
21 | 'afterData': ReqFnWithAnyParams
22 | 'mockFnData': ReqFnWithAnyParams
23 | 'noAfterData': ReqFnWithAnyParams
24 | 'jsonpOptions': ReqFnWithAnyParams
25 | 'beforeFnCookie': ReqFnWithAnyParams
26 | 'mockObjectData': ReqFnWithAnyParams
27 | 'empty-array-params': ReqFnWithAnyParams
28 | 'ap': ReqFn & {
29 | (
30 | params: { param1?: any, param2?: any },
31 | options?: RuntimeOptions
32 | ): Promise
33 | }
34 | 'op': ReqFn & {
35 | (
36 | params: { param1?: any, param2?: any, param3: any },
37 | options?: RuntimeOptions
38 | ): Promise
39 | }
40 | }
41 |
42 | export const fakePostApi: {
43 | 'ct': ReqFnWithAnyParams
44 | 'oh': ReqFnWithAnyParams
45 | 'pj': ReqFnWithAnyParams
46 | 'eap': ReqFnWithAnyParams
47 | 'hap': ReqFnWithAnyParams
48 | 'ap': ReqFn & {
49 | (
50 | params: { param1?: any, param2?: any },
51 | options?: RuntimeOptions
52 | ): Promise
53 | }
54 | 'op': ReqFn & {
55 | (
56 | params: { param1?: any, param2?: any, param3: any },
57 | options?: RuntimeOptions
58 | ): Promise
59 | }
60 | }
61 |
62 | export const fakeFnApi: {
63 | 'fp': ReqFn & {
64 | (
65 | params: { t?: any },
66 | options?: RuntimeOptions
67 | ): Promise
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/examples/apis-web/index.js:
--------------------------------------------------------------------------------
1 | import TuaApi from '../../src'
2 |
3 | const tuaApi = new TuaApi({
4 | host: 'http://example-base.com/',
5 | // 默认用 jsonp 的方式,不填默认用 axios
6 | reqType: 'jsonp',
7 | })
8 |
9 | // 使用中间件
10 | tuaApi.use(async (ctx, next) => {
11 | // 请求发起前
12 | // console.log('before: ', ctx)
13 |
14 | await next()
15 |
16 | // 响应返回后
17 | // console.log('after: ', ctx)
18 | })
19 |
20 | export const fakeFnApi = tuaApi.getApi(require('./fake-fn').default)
21 | export const fakeGetApi = tuaApi.getApi(require('./fake-get').default)
22 | export const fakePostApi = tuaApi.getApi(require('./fake-post').default)
23 |
--------------------------------------------------------------------------------
/globals.d.ts:
--------------------------------------------------------------------------------
1 | interface Wx {
2 | request: jest.Mocked
3 | hideLoading: jest.Mocked
4 | showLoading: jest.Mocked
5 | hideNavigationBarLoading: jest.Mocked
6 | showNavigationBarLoading: jest.Mocked
7 |
8 | // just for test
9 | __TEST_DATA__: {
10 | testData?: any
11 | isTestFail?: boolean
12 | }
13 | }
14 |
15 | declare const wx: Wx
16 |
17 | declare namespace NodeJS {
18 | interface Global {
19 | wx: Wx
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bail: true,
3 | clearMocks: true,
4 | transform: {
5 | '^.+\\.js$': 'babel-jest',
6 | },
7 | moduleNameMapper: {
8 | '@/(.*)$': '/src/$1',
9 | '@examples/(.*)$': '/examples/$1',
10 | },
11 | collectCoverage: true,
12 | collectCoverageFrom: [
13 | 'src/**',
14 | '!src/index.d.ts',
15 | ],
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tua-api",
3 | "version": "1.7.0",
4 | "description": "🏗 A common tool helps converting configs to api functions",
5 | "main": "dist/TuaApi.cjs.js",
6 | "module": "dist/TuaApi.esm.js",
7 | "unpkg": "dist/TuaApi.umd.js",
8 | "jsdelivr": "dist/TuaApi.umd.js",
9 | "types": "src/index.d.ts",
10 | "files": [
11 | "src",
12 | "dist",
13 | "examples"
14 | ],
15 | "scripts": {
16 | "cov": "open coverage/lcov-report/index.html",
17 | "docs": "vuepress dev docs",
18 | "docs:build": "vuepress build docs",
19 | "lint": "eslint --fix . docs/.vuepress/ --ignore-path .gitignore",
20 | "test": "cross-env NODE_ENV=test jest",
21 | "test:tdd": "cross-env NODE_ENV=test jest --watch",
22 | "prebuild": "rimraf dist/* & npm run test",
23 | "build": "cross-env NODE_ENV=production rollup -c",
24 | "deploy": "npm run docs:build && gh-pages -m \"[ci skip]\" -d docs/.vuepress/dist",
25 | "next:pm": "npm --no-git-tag-version version preminor",
26 | "next:pr": "npm --no-git-tag-version version prerelease",
27 | "pub": "npm run build && npm publish",
28 | "pub:n": "npm run build && npm publish --tag next"
29 | },
30 | "husky": {
31 | "hooks": {
32 | "pre-push": "npm test",
33 | "pre-commit": "lint-staged",
34 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
35 | }
36 | },
37 | "lint-staged": {
38 | "{src,test}/**/*.js": [
39 | "eslint --fix"
40 | ]
41 | },
42 | "eslintIgnore": [
43 | "dist/*",
44 | "!.eslintrc.js",
45 | "package.json"
46 | ],
47 | "dependencies": {
48 | "axios": "^0.21.1",
49 | "fetch-jsonp": "^1.1.3",
50 | "koa-compose": "^4.1.0"
51 | },
52 | "devDependencies": {
53 | "@babel/core": "^7.10.5",
54 | "@babel/plugin-external-helpers": "^7.10.4",
55 | "@babel/plugin-proposal-decorators": "^7.10.5",
56 | "@babel/plugin-proposal-object-rest-spread": "^7.10.4",
57 | "@babel/preset-env": "^7.10.4",
58 | "@commitlint/cli": "^9.1.1",
59 | "@commitlint/config-conventional": "^9.1.1",
60 | "@rollup/plugin-babel": "^5.1.0",
61 | "@rollup/plugin-commonjs": "^14.0.0",
62 | "@rollup/plugin-json": "^4.1.0",
63 | "@rollup/plugin-node-resolve": "^8.4.0",
64 | "@rollup/plugin-replace": "^2.3.3",
65 | "@types/jest": "^26.0.5",
66 | "all-contributors-cli": "^6.16.1",
67 | "axios-mock-adapter": "^1.18.2",
68 | "babel-core": "^7.0.0-bridge.0",
69 | "babel-eslint": "^10.1.0",
70 | "babel-jest": "^26.1.0",
71 | "codecov": "^3.7.1",
72 | "cross-env": "^7.0.2",
73 | "eslint": "^7.5.0",
74 | "eslint-config-standard": "^14.1.1",
75 | "eslint-plugin-import": "^2.22.0",
76 | "eslint-plugin-node": "^11.1.0",
77 | "eslint-plugin-promise": "^4.2.1",
78 | "eslint-plugin-standard": "^4.0.1",
79 | "gh-pages": "^3.1.0",
80 | "husky": "^4.2.5",
81 | "jest": "^26.1.0",
82 | "lint-staged": "^10.2.11",
83 | "rimraf": "^3.0.2",
84 | "rollup": "^2.22.1",
85 | "rollup-plugin-eslint": "^7.0.0",
86 | "rollup-plugin-terser": "^6.1.0",
87 | "typescript": "^3.9.7",
88 | "vuepress": "^1.5.2"
89 | },
90 | "repository": {
91 | "type": "git",
92 | "url": "git+https://github.com/tuateam/tua-api.git"
93 | },
94 | "homepage": "https://tuateam.github.io/tua-api/",
95 | "author": "StEve Young",
96 | "license": "MIT"
97 | }
98 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import json from '@rollup/plugin-json'
2 | import babel from '@rollup/plugin-babel'
3 | import replace from '@rollup/plugin-replace'
4 | import commonjs from '@rollup/plugin-commonjs'
5 | import { eslint } from 'rollup-plugin-eslint'
6 | import { terser } from 'rollup-plugin-terser'
7 | import nodeResolve from '@rollup/plugin-node-resolve'
8 |
9 | import pkg from './package.json'
10 |
11 | const input = 'src/index.js'
12 | const banner = `/* ${pkg.name} version ${pkg.version} */`
13 |
14 | const output = {
15 | cjs: {
16 | file: pkg.main,
17 | banner,
18 | format: 'cjs',
19 | exports: 'named',
20 | },
21 | esm: {
22 | file: pkg.module,
23 | banner,
24 | format: 'esm',
25 | },
26 | umd: {
27 | file: pkg.unpkg,
28 | name: 'TuaApi',
29 | banner,
30 | format: 'umd',
31 | exports: 'named',
32 | globals: {
33 | axios: 'axios',
34 | 'fetch-jsonp': 'fetchJsonp',
35 | },
36 | },
37 | }
38 | const plugins = [
39 | eslint(),
40 | json(),
41 | nodeResolve(),
42 | commonjs(),
43 | babel({ babelHelpers: 'bundled' }),
44 | ]
45 | const env = 'process.env.NODE_ENV'
46 | const external = ['axios', 'fetch-jsonp']
47 |
48 | export default [{
49 | input,
50 | output: [output.cjs, output.esm],
51 | plugins,
52 | external,
53 | }, {
54 | input,
55 | output: output.umd,
56 | external,
57 | plugins: [
58 | ...plugins,
59 | replace({ [env]: '"development"' }),
60 | ],
61 | }, {
62 | input,
63 | output: {
64 | ...output.umd,
65 | file: 'dist/TuaApi.umd.min.js',
66 | },
67 | external,
68 | plugins: [
69 | ...plugins,
70 | replace({ [env]: '"production"' }),
71 | terser({
72 | output: {
73 | /* eslint-disable */
74 | ascii_only: true,
75 | },
76 | }),
77 | ],
78 | }]
79 |
--------------------------------------------------------------------------------
/src/adapters/axios.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | import { DEFAULT_HEADER } from '../constants'
4 | import { logger, isFormData, isUndefined, getParamStrFromObj } from '../utils'
5 |
6 | /**
7 | * 获取使用 axios 发起请求后的 promise 对象
8 | * @param {object} options
9 | */
10 | export const getAxiosPromise = ({
11 | url,
12 | data,
13 | method,
14 | headers,
15 | crossDomain = true,
16 | withCredentials = true,
17 | transformRequest,
18 | ...rest
19 | }) => {
20 | const isFD = isFormData(data)
21 | const isPost = method.toLowerCase() === 'post'
22 |
23 | logger.log(`Req Url: ${url}`)
24 | if (data && (Object.keys(data).length || isFD)) {
25 | logger.log('Req Data:', data)
26 | }
27 |
28 | // 优先使用用户的配置
29 | if (isUndefined(transformRequest)) {
30 | transformRequest = isFD
31 | ? null
32 | : isPost
33 | // 如果使用 post 的请求方式,自动对其 stringify
34 | ? x => JSON.stringify(x)
35 | : getParamStrFromObj
36 | }
37 | if (isUndefined(headers)) {
38 | headers = isPost
39 | ? { 'Content-Type': 'application/json;charset=utf-8' }
40 | : DEFAULT_HEADER
41 | }
42 |
43 | return axios({
44 | url,
45 | data,
46 | method,
47 | headers,
48 | crossDomain,
49 | withCredentials,
50 | transformRequest,
51 | ...rest,
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/src/adapters/index.js:
--------------------------------------------------------------------------------
1 | export { getWxPromise } from './wx'
2 | export { getAxiosPromise } from './axios'
3 | export { getFetchJsonpPromise } from './jsonp'
4 |
--------------------------------------------------------------------------------
/src/adapters/jsonp.js:
--------------------------------------------------------------------------------
1 | import { logger } from '../utils'
2 |
3 | const fetchJsonp = require('fetch-jsonp')
4 |
5 | // 获取发起 jsonp 请求后的 promise 对象
6 | export const getFetchJsonpPromise = ({ url, jsonpOptions }) => {
7 | logger.log(`Jsonp Url: ${url}`)
8 |
9 | return fetchJsonp(url, jsonpOptions)
10 | .then(res => res.json())
11 | .then(data => ({ data }))
12 | }
13 |
--------------------------------------------------------------------------------
/src/adapters/wx.js:
--------------------------------------------------------------------------------
1 | import { logger, promisifyWxApi } from '../utils'
2 | import { ERROR_STRINGS, WX_VALID_METHODS } from '../constants'
3 |
4 | /**
5 | * 获取使用 wx 发起请求后的 promise 对象
6 | * @param {object} options
7 | */
8 | export const getWxPromise = ({
9 | url,
10 | data,
11 | method,
12 | header,
13 | fullUrl,
14 | isShowLoading = true,
15 | showLoadingFn = () => wx.showLoading({ title: '加载中' }),
16 | hideLoadingFn = wx.hideLoading.bind(wx),
17 | ...rest
18 | }) => {
19 | method = method.toUpperCase()
20 |
21 | if (method === 'GET') {
22 | logger.log(`Req Url: ${fullUrl}`)
23 | } else {
24 | logger.log(`Req Url: ${url}`)
25 | if (data && Object.keys(data).length) {
26 | logger.log('Req Data:', data)
27 | }
28 | }
29 |
30 | // 展示 loading
31 | isShowLoading && showLoadingFn()
32 |
33 | if (WX_VALID_METHODS.indexOf(method) === -1) {
34 | return Promise.reject(Error(ERROR_STRINGS.unknownMethodFn(method)))
35 | }
36 |
37 | return promisifyWxApi(wx.request)({
38 | ...rest,
39 | url,
40 | data,
41 | header,
42 | method,
43 | complete: () => {
44 | // 同步隐藏 loading
45 | isShowLoading && hideLoadingFn()
46 | /* istanbul ignore next */
47 | rest.complete && rest.complete()
48 | },
49 | })
50 | }
51 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | // 支持的请求类型
2 | const VALID_REQ_TYPES = ['wx', 'axios', 'jsonp', 'custom']
3 |
4 | // 小程序中合法的请求方法
5 | const WX_VALID_METHODS = ['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']
6 |
7 | // 默认请求头
8 | const DEFAULT_HEADER = { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' }
9 |
10 | // 错误信息
11 | const ERROR_STRINGS = {
12 | noData: 'no data!',
13 | argsType: 'the first parameter must be an object!',
14 | middleware: 'middleware must be a function!',
15 | reqTypeAndCustomFetch: 'reqType or customFetch only!',
16 |
17 | reqTypeFn: (reqType) => `invalid reqType: "${reqType}", ` +
18 | `support these reqTypes: ["${VALID_REQ_TYPES.join('", "')}"].`,
19 | unknownMethodFn: method => `unknown method: "${method}"!`,
20 | requiredParamFn: (apiName, param) => `${apiName} must pass required param: "${param}"!`,
21 | }
22 |
23 | export {
24 | ERROR_STRINGS,
25 | DEFAULT_HEADER,
26 | VALID_REQ_TYPES,
27 | WX_VALID_METHODS,
28 | }
29 |
--------------------------------------------------------------------------------
/src/exportUtils.js:
--------------------------------------------------------------------------------
1 | import {
2 | map,
3 | pipe,
4 | filter,
5 | values,
6 | flatten,
7 | mergeAll,
8 | } from './utils/'
9 |
10 | /**
11 | * 将各个发起 api 的函数的 key 与其绑定,与 TuaStorage 配合使用效果更佳
12 | * apis: { api1: apiMap1, api2: apiMap2 }
13 | * apiMap: {
14 | * path1: { [Function: path1] key: key1 },
15 | * path2: { [Function: path2] key: key2 },
16 | * ...
17 | * }
18 | *
19 | * 转换成 {
20 | * key1: [Function: path1] key: key1,
21 | * key2: [Function: path2] key: key2,
22 | * ...
23 | * }
24 | * @param {object} apis
25 | * @return {object}
26 | */
27 | const getSyncFnMapByApis = pipe(
28 | values,
29 | map(values),
30 | flatten,
31 | map(val => ({ [val.key]: val })),
32 | mergeAll,
33 | )
34 |
35 | /**
36 | * 过滤出有默认参数的接口(接口参数非数组,且不含有 isRequired/required)
37 | * @param {object} syncFnMap
38 | * @return {Array} keys 所有有默认参数的接口名称
39 | */
40 | const getPreFetchFnKeysBySyncFnMap = (syncFnMap) => pipe(
41 | Object.keys,
42 | filter((key) => {
43 | const { params } = syncFnMap[key]
44 |
45 | if (Array.isArray(params)) return false
46 |
47 | // 当前参数不是必须的
48 | const isParamNotRequired = (key) => (
49 | typeof params[key] !== 'object' ||
50 | // 兼容 vue 的写法
51 | (!params[key].isRequired && !params[key].required)
52 | )
53 |
54 | return Object.keys(params).every(isParamNotRequired)
55 | }),
56 | map(key => ({ key })),
57 | )(syncFnMap)
58 |
59 | export {
60 | getSyncFnMapByApis,
61 | getPreFetchFnKeysBySyncFnMap,
62 | }
63 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Options as JsonpOptions } from 'fetch-jsonp'
2 | import {
3 | AxiosResponse,
4 | AxiosRequestConfig as AxiosOptions,
5 | } from 'axios'
6 |
7 | /* -- types -- */
8 | export type AnyFunction = (...args: any[]) => any
9 | export type AnyPromiseFunction = (...args: any[]) => Promise
10 |
11 | export type Mock = AnyFunction | any
12 |
13 | export type ApiConfig = WxApiConfig | WebApiConfig
14 |
15 | export type ParamsConfig = string[] | ParamsObject | ((args?: object) => object)
16 |
17 | export type Middleware = (ctx: T, next: () => Promise) => Promise
18 |
19 | export type RuntimeOptions = WxRuntimeOptions | WebRuntimeOptions
20 |
21 | export type ReqType = (
22 | | 'wx' | 'WX'
23 | | 'axios' | 'AXIOS'
24 | | 'jsonp' | 'JSONP'
25 | | 'custom' | 'CUSTOM'
26 | )
27 |
28 | export type Method = (
29 | | 'get' | 'GET'
30 | | 'put' | 'PUT'
31 | | 'head' | 'HEAD'
32 | | 'post' | 'POST'
33 | | 'trace' | 'TRACE'
34 | | 'delete' | 'DELETE'
35 | | 'connect' | 'CONNECT'
36 | | 'options' | 'OPTIONS'
37 | )
38 |
39 | /* -- interfaces -- */
40 |
41 | export interface ParamsObject {
42 | [k: string]: (
43 | | { required: boolean }
44 | | { isRequired: boolean }
45 | | any
46 | )
47 | }
48 |
49 | export interface CtxReq {
50 | // deprecated
51 | host: string
52 | baseUrl: string
53 | // deprecated
54 | type: Method
55 | method: Method
56 |
57 | mock: Mock
58 | path: string
59 | prefix: string
60 | reqType: ReqType
61 | reqParams: object
62 | reqFnParams: object
63 | callbackName: string
64 | axiosOptions: AxiosOptions
65 | jsonpOptions: JsonpOptions
66 | [k: string]: any
67 | }
68 | export interface CtxRes extends AxiosResponse {
69 | data: any
70 | rawData: any
71 | error?: Error
72 | [k: string]: any
73 | }
74 |
75 | export interface Ctx {
76 | req: CtxReq
77 | res: CtxRes
78 | endTime: number
79 | reqTime: number
80 | startTime: number
81 | [k: string]: any
82 | }
83 |
84 | export interface BaseApiConfig {
85 | // deprecated
86 | host?: string
87 | baseUrl?: string
88 | // deprecated
89 | type?: Method
90 | method?: Method
91 |
92 | mock?: Mock
93 | prefix?: string
94 | reqType?: ReqType
95 | afterFn?: (args: [U?, Ctx?]) => Promise
96 | beforeFn?: () => Promise
97 | middleware?: Middleware[]
98 | customFetch?: AnyPromiseFunction
99 | commonParams?: object | ((args?: object) => object),
100 | axiosOptions?: AxiosOptions
101 | jsonpOptions?: JsonpOptions
102 | useGlobalMiddleware?: boolean
103 | [k: string]: any
104 | }
105 |
106 | // for web
107 | export interface WebApiConfig extends BaseApiConfig {
108 | pathList: (BaseApiConfig & {
109 | path: string
110 | name?: string
111 | params?: ParamsConfig
112 | })[]
113 | }
114 |
115 | // for wechat miniprogram
116 | export interface WxApiConfig extends WebApiConfig {
117 | isShowLoading?: boolean
118 | showLoadingFn?: AnyFunction
119 | hideLoadingFn?: AnyFunction
120 | }
121 |
122 | export interface RuntimeOptionsOnly {
123 | apiName?: string
124 | fullPath?: string
125 | callbackName?: string
126 | }
127 | export interface WxRuntimeOptions extends WxApiConfig, RuntimeOptionsOnly { }
128 | export interface WebRuntimeOptions extends WebApiConfig, RuntimeOptionsOnly { }
129 |
130 | export interface Api {
131 | key: string
132 | mock: Mock
133 | params: ParamsConfig
134 | (
135 | params?: U,
136 | runtimeOptions?: RuntimeOptions
137 | ): Promise
138 | }
139 | export interface Apis { [k: string]: SyncFnMap }
140 | export interface SyncFnMap { [k: string]: Api }
141 |
142 | export interface TuaApiClass {
143 | new(args?: {
144 | // deprecated
145 | host?: string
146 | baseUrl?: string
147 |
148 | reqType?: string
149 | middleware?: Middleware[]
150 | customFetch?: AnyPromiseFunction
151 | axiosOptions?: AxiosOptions
152 | jsonpOptions?: JsonpOptions
153 | defaultErrorData?: any
154 | }): TuaApiInstance
155 | }
156 |
157 | export interface TuaApiInstance {
158 | use: (fn: Middleware) => TuaApiInstance
159 | getApi: (apiConfig: ApiConfig) => SyncFnMap
160 | }
161 |
162 | /* -- export utils -- */
163 |
164 | export function getSyncFnMapByApis (apis: Apis): SyncFnMap
165 | export function getPreFetchFnKeysBySyncFnMap (syncFnMap: SyncFnMap): Api[]
166 |
167 | /* -- export default -- */
168 |
169 | declare const TuaApi: TuaApiClass
170 | export default TuaApi
171 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | import koaCompose from 'koa-compose'
3 |
4 | import { version } from '../package.json'
5 | import {
6 | map,
7 | pipe,
8 | isWx,
9 | runFn,
10 | logger,
11 | mergeAll,
12 | apiConfigToReqFnParams,
13 | } from './utils'
14 | import {
15 | ERROR_STRINGS,
16 | VALID_REQ_TYPES,
17 | } from './constants'
18 | import {
19 | getWxPromise,
20 | getAxiosPromise,
21 | getFetchJsonpPromise,
22 | } from './adapters/'
23 | import {
24 | formatResDataMiddleware,
25 | recordReqTimeMiddleware,
26 | setReqFnParamsMiddleware,
27 | recordStartTimeMiddleware,
28 | formatReqParamsMiddleware,
29 | } from './middlewareFns'
30 |
31 | logger.log(`Version: ${version}`)
32 |
33 | class TuaApi {
34 | /**
35 | * @param {object} [options]
36 | * @param {string} [options.host] 服务器基础地址,例如 https://example.com/
37 | * @param {string} [options.baseUrl] 服务器基础地址,例如 https://example.com/
38 | * @param {string} [options.reqType] 使用什么工具发(axios/jsonp/wx)
39 | * @param {function[]} [options.middleware] 中间件函数数组
40 | * @param {function} [options.customFetch] 自定义请求函数
41 | * @param {object} [options.axiosOptions] 透传 axios 配置参数
42 | * @param {object} [options.jsonpOptions] 透传 fetch-jsonp 配置参数
43 | * @param {object} [options.defaultErrorData] 出错时的默认数据
44 | */
45 | constructor ({
46 | host,
47 | baseUrl = host,
48 | reqType,
49 | middleware = [],
50 | customFetch,
51 | axiosOptions = {},
52 | jsonpOptions = {},
53 | defaultErrorData = { code: 999, msg: '出错啦!' },
54 | } = {}) {
55 | this.baseUrl = baseUrl
56 | this.reqType = reqType !== undefined
57 | ? reqType.toLowerCase()
58 | : (isWx() ? 'wx' : 'axios')
59 | this.middleware = middleware
60 | this.customFetch = customFetch
61 | this.axiosOptions = axiosOptions
62 | this.jsonpOptions = jsonpOptions
63 | this.defaultErrorData = defaultErrorData
64 |
65 | this._checkReqType(this.reqType)
66 |
67 | if (host) {
68 | logger.warn(
69 | '[host] will be deprecated, please use [baseUrl] instead!\n' +
70 | '[host] 属性将被废弃, 请用 [baseUrl] 替代!',
71 | )
72 | }
73 | if (reqType && reqType !== 'custom' && customFetch) {
74 | throw TypeError(ERROR_STRINGS.reqTypeAndCustomFetch)
75 | }
76 |
77 | return this
78 | }
79 |
80 | /* -- 各种对外暴露方法 -- */
81 |
82 | /**
83 | * 添加一个中间件函数
84 | * @param {function} fn
85 | * @return {object} self
86 | */
87 | use (fn) {
88 | if (typeof fn !== 'function') {
89 | throw TypeError(ERROR_STRINGS.middleware)
90 | }
91 | this.middleware.push(fn)
92 |
93 | return this
94 | }
95 |
96 | /**
97 | * 根据 apiConfig 生成请求函数组成的 map
98 | * @param {object} apiConfig
99 | * @return {object}
100 | */
101 | getApi (apiConfig) {
102 | return pipe(
103 | apiConfigToReqFnParams,
104 | map(this._getOneReqMap.bind(this)),
105 | mergeAll,
106 | )(apiConfig)
107 | }
108 |
109 | /* -- 各种私有方法 -- */
110 |
111 | /**
112 | * 根据 reqType 和 type 决定调用哪个库
113 | * @param {object} options
114 | * @param {Object|Function} options.mock 模拟的响应数据或是生成数据的函数
115 | * @param {string} options.url 接口地址
116 | * @param {string} options.method 接口请求类型 get/post...
117 | * @param {string} options.fullUrl 完整接口地址
118 | * @param {string} options.reqType 使用什么工具发(axios/jsonp/wx)
119 | * @param {object} options.reqParams 请求参数
120 | * @param {object} options.header 请求的 header
121 | * @param {function} [options.customFetch] 自定义请求函数
122 | * @param {string} options.callback 使用 jsonp 时标识回调函数的名称
123 | * @param {string} options.callbackName 使用 jsonp 时的回调函数名
124 | * @param {object} options.axiosOptions 透传 axios 配置参数
125 | * @param {object} options.jsonpOptions 透传 fetch-jsonp 配置参数
126 | * @return {Promise}
127 | */
128 | _reqFn (options) {
129 | const {
130 | url,
131 | mock,
132 | header,
133 | method: _method,
134 | fullUrl,
135 | reqType: _reqType,
136 | reqParams: data,
137 | callback,
138 | callbackName,
139 | axiosOptions,
140 | jsonpOptions,
141 | ...rest
142 | } = options
143 |
144 | // check type
145 | this._checkReqType(_reqType)
146 |
147 | // mock data
148 | if (mock) {
149 | const resData = { ...runFn(mock, data) }
150 |
151 | return Promise.resolve({ data: resData })
152 | }
153 |
154 | const method = _method.toLowerCase()
155 | const reqType = _reqType.toLowerCase()
156 |
157 | if (reqType === 'custom') {
158 | return rest.customFetch({ url, data, method, header, ...rest })
159 | }
160 |
161 | if (reqType === 'wx') {
162 | return getWxPromise({ url, fullUrl, data, method, header, ...rest })
163 | }
164 |
165 | if (reqType === 'axios' || method === 'post') {
166 | const params = {
167 | ...axiosOptions,
168 | url: method === 'get' ? fullUrl : url,
169 | data: method === 'get' ? {} : data,
170 | method,
171 | headers: header,
172 | }
173 |
174 | return getAxiosPromise(params)
175 | }
176 |
177 | // 防止接口返回非英文时报错
178 | jsonpOptions.charset = jsonpOptions.charset || 'UTF-8'
179 | jsonpOptions.jsonpCallback = callback || jsonpOptions.jsonpCallback
180 | jsonpOptions.jsonpCallbackFunction = callbackName || jsonpOptions.jsonpCallbackFunction
181 |
182 | return getFetchJsonpPromise({ url: fullUrl, jsonpOptions })
183 | }
184 |
185 | /**
186 | * 检查 reqType 是否合法
187 | */
188 | _checkReqType (reqType) {
189 | if (VALID_REQ_TYPES.indexOf(reqType) !== -1) return
190 |
191 | throw TypeError(ERROR_STRINGS.reqTypeFn(reqType))
192 | }
193 |
194 | /**
195 | * 组合生成中间件函数
196 | * @param {function[]} middleware
197 | * @param {Boolean} useGlobalMiddleware 是否使用全局中间件
198 | */
199 | _getMiddlewareFn (middleware, useGlobalMiddleware) {
200 | const middlewareFns = useGlobalMiddleware
201 | ? this.middleware.concat(middleware)
202 | : middleware
203 |
204 | return koaCompose([
205 | // 记录开始时间
206 | recordStartTimeMiddleware,
207 | // 格式化生成请求参数
208 | formatReqParamsMiddleware,
209 | // 业务侧中间件函数数组
210 | ...middlewareFns,
211 | // 生成 _reqFn 参数
212 | setReqFnParamsMiddleware,
213 | // 统一转换响应数据为对象
214 | formatResDataMiddleware,
215 | // 记录结束时间
216 | recordReqTimeMiddleware,
217 | // 发起请求
218 | (ctx, next) => next()
219 | .then(() => this._reqFn(ctx.req.reqFnParams))
220 | // 暂存出错,保证 afterFn 能执行(finally)
221 | .catch((error) => ({
222 | // 浅拷贝一份默认出错值
223 | data: { ...this.defaultErrorData },
224 | error,
225 | }))
226 | .then(res => { ctx.res = res }),
227 | ])
228 | }
229 |
230 | /**
231 | * 接受 api 对象,返回待接收参数的单个 api 函数的对象
232 | * @param {object} options
233 | * @param {string} options.type 接口请求类型 get/post...
234 | * @param {string} options.method 接口请求类型 get/post...
235 | * @param {Object|Function} options.mock 模拟的响应数据或是生成数据的函数
236 | * @param {string} options.name 自定义的接口名称
237 | * @param {string} options.path 接口结尾路径
238 | * @param {String[] | object} options.params 接口参数数组
239 | * @param {string} options.prefix 接口前缀
240 | * @param {function} options.afterFn 在请求完成后执行的钩子函数(将被废弃)
241 | * @param {function} options.beforeFn 在请求发起前执行的钩子函数(将被废弃)
242 | * @param {function[]} options.middleware 中间件函数数组
243 | * @param {function} [options.customFetch] 自定义请求函数
244 | * @param {Boolean} options.useGlobalMiddleware 是否使用全局中间件
245 | * @param {string} options.baseUrl 服务器地址
246 | * @param {string} options.reqType 使用什么工具发
247 | * @param {object} options.axiosOptions 透传 axios 配置参数
248 | * @param {object} options.jsonpOptions 透传 fetch-jsonp 配置参数
249 | * @return {object} 以 apiName 为 key,请求函数为值的对象
250 | */
251 | _getOneReqMap ({
252 | type,
253 | method = type,
254 | mock,
255 | name,
256 | path,
257 | params: rawParams = {},
258 | prefix,
259 | afterFn = ([x]) => x,
260 | beforeFn = Promise.resolve.bind(Promise),
261 | middleware = [],
262 | useGlobalMiddleware = true,
263 | ...rest
264 | }) {
265 | if (type) {
266 | logger.warn(
267 | '[type] will be deprecated, please use [method] instead!\n' +
268 | '[type] 属性将被废弃, 请用 [method] 替代!',
269 | )
270 | }
271 |
272 | // 优先使用 name
273 | const apiName = name || path
274 | // 默认值
275 | method = method || 'get'
276 | // 向前兼容
277 | type = method
278 |
279 | /* 合并全局默认值 */
280 | if (rest.reqType && rest.customFetch) {
281 | if (rest.reqType.toLowerCase() !== 'custom') {
282 | logger.warn(ERROR_STRINGS.reqTypeAndCustomFetch)
283 | }
284 | rest.reqType = 'custom'
285 | } else if (rest.customFetch || this.customFetch) {
286 | // 没有配置 reqType,但配了公共配置或默认配置的 customFetch
287 | rest.reqType = 'custom'
288 | } else {
289 | // 没有配置 customFetch
290 | rest.reqType = rest.reqType || this.reqType
291 | }
292 | rest.baseUrl = rest.baseUrl || this.baseUrl
293 | rest.customFetch = rest.customFetch || this.customFetch
294 | rest.axiosOptions = rest.axiosOptions
295 | ? { ...this.axiosOptions, ...rest.axiosOptions }
296 | : this.axiosOptions
297 | rest.jsonpOptions = rest.jsonpOptions
298 | ? { ...this.jsonpOptions, ...rest.jsonpOptions }
299 | : this.jsonpOptions
300 |
301 | /**
302 | * 被业务侧调用的函数
303 | * @param {object} args 接口参数(覆盖默认值)
304 | * @param {object} runtimeOptions 运行时配置
305 | * @return {Promise}
306 | */
307 | const apiFn = (args, runtimeOptions = {}) => {
308 | args = args || {}
309 |
310 | const params = runFn(rawParams, args)
311 |
312 | // 最终的运行时配置,runtimeOptions 有最高优先级
313 | const runtimeParams = {
314 | type,
315 | path,
316 | params,
317 | prefix,
318 | apiName,
319 | fullPath: `${prefix}/${path}`,
320 | ...rest,
321 | ...runtimeOptions,
322 | }
323 |
324 | // 向前兼容
325 | runtimeParams.host = runtimeParams.host || runtimeParams.baseUrl
326 | runtimeParams.method = runtimeParams.method || runtimeParams.type
327 | runtimeParams.baseUrl = runtimeParams.baseUrl || runtimeParams.host
328 |
329 | // 请求的上下文信息
330 | const ctx = {
331 | req: { args, mock: apiFn.mock, reqFnParams: {}, ...runtimeParams },
332 | }
333 |
334 | // 执行完 beforeFn 后执行的函数
335 | const beforeFnCb = (rArgs = {}) => {
336 | // 传递请求头
337 | if (rArgs.header) {
338 | ctx.req.reqFnParams.header = rArgs.header
339 | }
340 |
341 | if (!rArgs.params) return
342 |
343 | // 合并 beforeFn 中传入的 params
344 | ctx.req.params = Array.isArray(params)
345 | ? rArgs.params
346 | // 可以通过给 beforeFn 添加 params 返回值来添加通用参数
347 | : { ...params, ...rArgs.params }
348 | }
349 |
350 | // 中间件函数
351 | const middlewareFn = this._getMiddlewareFn(middleware, useGlobalMiddleware)
352 |
353 | return beforeFn()
354 | .then(beforeFnCb)
355 | .then(() => middlewareFn(ctx))
356 | .then(() => afterFn([ctx.res.data, ctx]))
357 | .then((data) => ctx.res.error
358 | ? Promise.reject(ctx.res.error)
359 | : data || ctx.res.data,
360 | )
361 | }
362 |
363 | apiFn.key = `${prefix}/${apiName}`
364 | apiFn.mock = mock
365 | apiFn.params = rawParams
366 |
367 | return { [apiName]: apiFn }
368 | }
369 | }
370 |
371 | export default TuaApi
372 | export * from './exportUtils'
373 |
--------------------------------------------------------------------------------
/src/middlewareFns.js:
--------------------------------------------------------------------------------
1 | import { ERROR_STRINGS } from './constants'
2 | import {
3 | runFn,
4 | isFormData,
5 | combineUrls,
6 | checkArrayParams,
7 | getParamStrFromObj,
8 | getDefaultParamObj,
9 | } from './utils'
10 |
11 | /**
12 | * 记录请求开始时间
13 | * @param {object} ctx 上下文对象
14 | * @param {Function} next 转移控制权给下一个中间件的函数
15 | */
16 | const recordStartTimeMiddleware = (ctx, next) => {
17 | ctx.startTime = Date.now()
18 |
19 | return next()
20 | }
21 |
22 | /**
23 | * 记录接受响应时间和请求总时间的中间件
24 | * @param {object} ctx 上下文对象
25 | * @param {Function} next 转移控制权给下一个中间件的函数
26 | */
27 | const recordReqTimeMiddleware = (ctx, next) => {
28 | return next().then(() => {
29 | ctx.endTime = Date.now()
30 | ctx.reqTime = Date.now() - ctx.startTime
31 | })
32 | }
33 |
34 | /**
35 | * 由于后台返回数据结构不统一,增加对于返回数组情况的兼容处理
36 | * 且对于 code 进行强制类型转换
37 | * @param {object} ctx 上下文对象(ctx.res.data 接口返回数据)
38 | * @param {Function} next 转移控制权给下一个中间件的函数
39 | */
40 | const formatResDataMiddleware = (ctx, next) => next().then(() => {
41 | const jsonData = ctx.res.data
42 | ctx.res.rawData = ctx.res.data
43 |
44 | if (!jsonData) return Promise.reject(Error(ERROR_STRINGS.noData))
45 |
46 | if (Array.isArray(jsonData)) {
47 | const [code, data, msg] = jsonData
48 | ctx.res.data = { code: +code, data, msg }
49 | } else {
50 | ctx.res.data = { ...jsonData, code: +jsonData.code }
51 | }
52 | })
53 |
54 | /**
55 | * 生成请求函数所需参数
56 | * @param {object} ctx 上下文对象
57 | * @param {Function} next 转移控制权给下一个中间件的函数
58 | */
59 | const formatReqParamsMiddleware = (ctx, next) => {
60 | const {
61 | args,
62 | params,
63 | commonParams: rawCommonParams,
64 | } = ctx.req
65 |
66 | if (typeof args !== 'object') {
67 | throw TypeError(ERROR_STRINGS.argsType)
68 | }
69 |
70 | if (isFormData(args) || Array.isArray(args)) {
71 | ctx.req.reqParams = args
72 |
73 | return next()
74 | }
75 |
76 | checkArrayParams(ctx.req)
77 |
78 | const commonParams = runFn(rawCommonParams, args)
79 | // 根据配置生成请求的参数
80 | ctx.req.reqParams = Array.isArray(params)
81 | ? { ...commonParams, ...args }
82 | : { ...getDefaultParamObj({ ...ctx.req, commonParams }), ...args }
83 |
84 | return next()
85 | }
86 |
87 | /**
88 | * 设置请求的 reqFnParams 参数,将被用于 _reqFn 函数
89 | * @param {object} ctx 上下文对象
90 | * @param {Function} next 转移控制权给下一个中间件的函数
91 | */
92 | const setReqFnParamsMiddleware = (ctx, next) => {
93 | const { path, prefix, reqParams, baseUrl, ...rest } = ctx.req
94 |
95 | // 请求地址
96 | const url = combineUrls(combineUrls(baseUrl, prefix), path)
97 | const paramsStr = getParamStrFromObj(reqParams)
98 | // 完整请求地址,将参数拼在 url 上,用于 get 请求
99 | const fullUrl = paramsStr ? `${url}?${paramsStr}` : url
100 |
101 | ctx.req.reqFnParams = {
102 | url,
103 | baseUrl,
104 | fullUrl,
105 | reqParams,
106 | ...rest,
107 | // 若是用户自己传递 reqFnParams 则优先级最高
108 | ...ctx.req.reqFnParams,
109 | }
110 |
111 | return next()
112 | }
113 |
114 | export {
115 | recordReqTimeMiddleware,
116 | formatResDataMiddleware,
117 | setReqFnParamsMiddleware,
118 | recordStartTimeMiddleware,
119 | formatReqParamsMiddleware,
120 | }
121 |
--------------------------------------------------------------------------------
/src/utils/combineUrls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a new URL by combining the specified URLs
3 | * @param {string} baseUrl The base URL
4 | * @param {string} relativeUrl The relative URL
5 | * @returns {string} The combined URL
6 | */
7 | function combineUrls (baseUrl = '', relativeUrl = '') {
8 | const strBaseUrl = baseUrl === null ? '' : String(baseUrl)
9 | const strRelativeUrl = relativeUrl === null ? '' : String(relativeUrl)
10 |
11 | if (!strRelativeUrl) return strBaseUrl
12 |
13 | return (
14 | strBaseUrl.replace(/\/+$/, '') +
15 | '/' +
16 | strRelativeUrl.replace(/^\/+/, '')
17 | )
18 | }
19 |
20 | export {
21 | combineUrls,
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/fp.js:
--------------------------------------------------------------------------------
1 | const map = (fn) => (arr) => Array.isArray(arr)
2 | ? arr.map(fn)
3 | : pipe(
4 | Object.keys,
5 | map(key => ({ [key]: fn(arr[key]) })),
6 | mergeAll,
7 | )(arr)
8 |
9 | const join = str => arr => arr.join(str)
10 | const concat = val => arr => arr.concat(val)
11 | const filter = fn => arr => arr.filter(fn)
12 | const values = obj => map(k => obj[k])(Object.keys(obj))
13 | const reduce = (fn, val) => (arr) => !arr.length
14 | ? val
15 | : val == null
16 | ? arr.reduce(fn)
17 | : arr.reduce(fn, val)
18 |
19 | const flatten = reduce(
20 | (acc, cur) => Array.isArray(cur)
21 | ? compose(concat, flatten)(cur)(acc)
22 | : concat(cur)(acc),
23 | [],
24 | )
25 |
26 | const merge = (acc, cur) => ({ ...acc, ...cur })
27 | const mergeAll = reduce(merge, {})
28 |
29 | /**
30 | * 从左向右结合函数
31 | * @param {Function[]} funcs 函数数组
32 | */
33 | const pipe = (...funcs) => {
34 | if (funcs.length === 0) return arg => arg
35 | if (funcs.length === 1) return funcs[0]
36 |
37 | return funcs.reduce((a, b) => (...args) => b(a(...args)))
38 | }
39 |
40 | /**
41 | * 从右向左结合函数
42 | * @param {Function[]} funcs 函数数组
43 | */
44 | const compose = (...funcs) => {
45 | if (funcs.length === 0) return arg => arg
46 | if (funcs.length === 1) return funcs[0]
47 |
48 | return funcs.reduce((a, b) => (...args) => a(b(...args)))
49 | }
50 |
51 | export {
52 | map,
53 | join,
54 | pipe,
55 | merge,
56 | concat,
57 | reduce,
58 | filter,
59 | values,
60 | compose,
61 | flatten,
62 | mergeAll,
63 | }
64 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export * from './fp'
2 | export * from './mp'
3 | export * from './judge'
4 | export * from './logger'
5 | export * from './params'
6 | export * from './combineUrls'
7 |
--------------------------------------------------------------------------------
/src/utils/judge.js:
--------------------------------------------------------------------------------
1 | export const isWx = () => (
2 | typeof wx !== 'undefined' &&
3 | typeof wx.request === 'function'
4 | )
5 |
6 | export const isFormData = (val) => (
7 | (typeof FormData !== 'undefined') &&
8 | (val instanceof FormData)
9 | )
10 |
11 | export const runFn = (fn, ...params) => {
12 | if (typeof fn === 'function') return fn(...params)
13 |
14 | return fn
15 | }
16 |
17 | export const isUndefined = val => typeof val === 'undefined'
18 |
--------------------------------------------------------------------------------
/src/utils/logger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 统一的日志输出函数,在测试环境时不输出
3 | * @param {string} type 输出类型 log|warn|error
4 | */
5 | const logByType = (type) => (...out) => {
6 | const env = process.env.NODE_ENV
7 | /* istanbul ignore next */
8 | if (env === 'test' || env === 'production') return
9 |
10 | /* istanbul ignore next */
11 | console[type]('[TUA-API]:', ...out)
12 | }
13 |
14 | export const logger = {
15 | log: logByType('log'),
16 | warn: logByType('warn'),
17 | error: logByType('error'),
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/mp.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 将小程序 api promise 化
3 | * @param {Function} fn
4 | */
5 | const promisifyWxApi = (fn) => (args = {}) => (
6 | new Promise((success, fail) => {
7 | fn({ fail, success, ...args })
8 | })
9 | )
10 |
11 | export {
12 | promisifyWxApi,
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/params.js:
--------------------------------------------------------------------------------
1 | import {
2 | map,
3 | pipe,
4 | join,
5 | merge,
6 | reduce,
7 | } from './fp'
8 | import { logger } from './logger'
9 | import { ERROR_STRINGS } from '../constants'
10 |
11 | /**
12 | * 将对象序列化为 queryString 的形式
13 | * @param {object} data
14 | * @returns {string}
15 | */
16 | const getParamStrFromObj = (data = {}) => pipe(
17 | Object.keys,
18 | map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`),
19 | join('&'),
20 | )(data)
21 |
22 | /**
23 | * 检查 params 长度和 args 的长度是否匹配,不匹配则打印告警
24 | * @param {object} options
25 | * @param {object} [options.args] 业务侧传递的请求参数
26 | * @param {array|object} options.params 配置中定义的接口数组
27 | * @param {string} [options.apiName] 接口名称
28 | * @return {Boolean} 检查结果(测试使用)
29 | */
30 | const checkArrayParams = ({ args, params, apiName }) => {
31 | if (!Array.isArray(params)) return true
32 |
33 | if (Object.keys(args).length !== params.length) {
34 | logger.warn(`${apiName}:传递参数长度与 apiConfig 中配置的不同!请检查!`)
35 | return false
36 | }
37 |
38 | return true
39 | }
40 |
41 | /**
42 | * 类似于 vue 的 props,检查传递的参数
43 | * @param {object} options
44 | * @param {object} [options.args] 调用时传递参数
45 | * @param {object} [options.params] 默认参数
46 | * @param {string} [options.apiName] 接口名字
47 | * @param {object} [options.commonParams] 公用默认参数
48 | */
49 | const getDefaultParamObj = ({
50 | args = {},
51 | params = {},
52 | apiName,
53 | commonParams = {},
54 | }) => pipe(
55 | Object.keys,
56 | map((key) => {
57 | const val = params[key]
58 | const isRequiredValUndefined =
59 | typeof val === 'object' &&
60 | // 兼容 vue 的写法
61 | (val.isRequired || val.required) &&
62 | args[key] == null
63 |
64 | if (isRequiredValUndefined) {
65 | logger.error(ERROR_STRINGS.requiredParamFn(apiName, key))
66 |
67 | /* istanbul ignore next */
68 | if (process.env.NODE_ENV === 'test') {
69 | throw TypeError(ERROR_STRINGS.requiredParamFn(apiName, key))
70 | }
71 | }
72 |
73 | const returnVal = typeof val === 'object' ? '' : val
74 |
75 | return { [key]: returnVal }
76 | }),
77 | reduce(merge, commonParams),
78 | )(params)
79 |
80 | /**
81 | * 合并 pathList 下的接口配置和上一级的公共配置
82 | * @param {{ pathList: object[], [k: string]: any }} options
83 | * @return {array} 请求所需参数数组
84 | */
85 | const apiConfigToReqFnParams = ({ pathList, ...rest }) =>
86 | map((pathObj) => ({ ...rest, ...pathObj }))(pathList)
87 |
88 | export {
89 | checkArrayParams,
90 | getDefaultParamObj,
91 | getParamStrFromObj,
92 | apiConfigToReqFnParams,
93 | }
94 |
--------------------------------------------------------------------------------
/test/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: { jest: true },
3 | globals: {
4 | FormData: true,
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/test/__mocks__/wxMock.js:
--------------------------------------------------------------------------------
1 | // mock wx
2 | global.wx = {
3 | request: jest.fn(({
4 | fail,
5 | success,
6 | complete,
7 | }) => {
8 | setTimeout(() => {
9 | complete && complete()
10 |
11 | const { isTestFail, testData } = wx.__TEST_DATA__
12 | if (isTestFail) return fail(Error('test'))
13 |
14 | success && success({ data: testData })
15 | }, 0)
16 | }),
17 | hideLoading: jest.fn(),
18 | showLoading: jest.fn(),
19 | hideNavigationBarLoading: jest.fn(),
20 | showNavigationBarLoading: jest.fn(),
21 |
22 | // 测试数据
23 | __TEST_DATA__: {
24 | isTestFail: false,
25 | testData: null,
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/test/__tests__/axios.test.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import MockAdapter from 'axios-mock-adapter'
3 |
4 | import fakePostConfig from '@examples/apis-web/fake-post'
5 | import { ERROR_STRINGS } from '@/constants'
6 | import { fakeGetApi, fakePostApi } from '@examples/apis-web/'
7 |
8 | const mock = new MockAdapter(axios)
9 |
10 | const params = { param1: 'steve', param2: 'young' }
11 |
12 | const reqAPUrl = 'http://example-base.com/fake-post/array-params'
13 | const reqOPUrl = 'http://example-base.com/fake-post/object-params'
14 | const reqGOPUrl = 'http://example-base.com/fake-get/object-params'
15 | const reqOHUrl = 'http://example-test.com/fake-post/own-baseUrl'
16 | const reqTAUrl = 'http://example-base.com/fake-get/req-type-axios?asyncCp=asyncCp'
17 | const reqEAPUrl = 'http://example-base.com/fake-post/empty-array-params'
18 | const reqMFDUrl = 'http://example-base.com/fake-get/mock-function-data'
19 | const reqBFCUrl = 'http://example-base.com/fake-get/beforeFn-cookie'
20 | const reqCTUrl = 'http://example-base.com/fake-post/custom-transformRequest'
21 | const reqPjUrl = 'http://example-base.com/fake-post/post-json'
22 | const reqRdUrl = 'http://example-base.com/fake-post/raw-data'
23 |
24 | describe('middleware', () => {
25 | test('change baseUrl before request', async () => {
26 | const data = { code: 0, data: 'custom baseUrl' }
27 | const reqHAPUrl = 'http://custom-baseUrl.com/fake-post/array-params'
28 | mock.onPost(reqHAPUrl).reply(200, data)
29 | const resData = await fakePostApi.hap()
30 |
31 | expect(resData).toEqual(data)
32 | })
33 | })
34 |
35 | describe('beforeFn cookie', () => {
36 | beforeEach(() => {
37 | // @ts-ignore
38 | mock.resetHistory()
39 | })
40 |
41 | test('set cookie by beforeFn', async () => {
42 | mock.onGet(reqBFCUrl).reply(200, {})
43 | await fakeGetApi.beforeFnCookie()
44 |
45 | expect(mock.history.get[0].headers.cookie).toBe('123')
46 | })
47 | })
48 |
49 | describe('mock data', () => {
50 | test('mock function data', async () => {
51 | mock.onGet(reqMFDUrl).reply(200, {})
52 | const resData = await fakeGetApi.mockFnData({ mockCode: 404 })
53 |
54 | expect(resData.code).toBe(404)
55 | })
56 | })
57 |
58 | describe('error handling', () => {
59 | test('non-object params', () => {
60 | // @ts-ignore
61 | return expect(fakePostApi.ap('a')).rejects.toEqual(TypeError(ERROR_STRINGS.argsType))
62 | })
63 |
64 | test('error', () => {
65 | mock.onPost(reqEAPUrl).networkError()
66 |
67 | return expect(fakePostApi.eap()).rejects.toEqual(Error('Network Error'))
68 | })
69 |
70 | test('must pass required params', () => {
71 | // @ts-ignore
72 | return expect(fakePostApi.op())
73 | .rejects.toEqual(Error(ERROR_STRINGS.requiredParamFn('op', 'param3')))
74 | })
75 | })
76 |
77 | describe('fake get requests', () => {
78 | beforeEach(() => {
79 | // @ts-ignore
80 | mock.resetHistory()
81 | })
82 |
83 | test('req-type-axios', async () => {
84 | const data = { code: 0, data: 'req-type-axios' }
85 | mock.onGet(reqTAUrl).reply(200, data)
86 | const resData = await fakeGetApi.rta()
87 |
88 | expect(resData).toEqual(data)
89 | })
90 |
91 | test('runtime get', async () => {
92 | const data = { code: 0, data: 'runtime get' }
93 | mock.onGet(reqAPUrl).reply(200, data)
94 | const resData = await fakePostApi.ap(null, {
95 | type: 'get',
96 | reqType: 'axios',
97 | commonParams: null,
98 | })
99 |
100 | expect(resData).toEqual(data)
101 | })
102 |
103 | test('required param', async () => {
104 | const data = [0, 'array data']
105 | mock.onGet(reqGOPUrl + '?param1=1217¶m2=steve¶m3=young').reply(200, data)
106 | const resData = await fakeGetApi.op({ param3: 'young' }, { reqType: 'axios' })
107 |
108 | expect(mock.history.get[0].params).toBe(undefined)
109 | expect(resData).toEqual({ code: 0, data: 'array data' })
110 | })
111 | })
112 |
113 | describe('fake post requests', () => {
114 | beforeEach(() => {
115 | // @ts-ignore
116 | mock.resetHistory()
117 | })
118 |
119 | test('own-baseUrl', async () => {
120 | const data = { code: 0, data: 'own-baseUrl' }
121 | mock.onPost(reqOHUrl).reply(200, data)
122 | const resData = await fakePostApi.oh()
123 |
124 | expect(resData).toEqual(data)
125 | })
126 |
127 | test('empty-array-params', async () => {
128 | const data = { code: 0, data: 'object data' }
129 | const arrayArgs = [1, 2]
130 | mock.onPost(reqEAPUrl).reply(200, data)
131 | const resData = await fakePostApi.eap(arrayArgs)
132 |
133 | expect(resData).toEqual(data)
134 | expect(mock.history.post[0].data).toEqual(JSON.stringify(arrayArgs))
135 | })
136 |
137 | test('array-params', async () => {
138 | const data = { code: 0, data: 'object data' }
139 | mock.onPost(reqAPUrl).reply(200, data)
140 | const resData = await fakePostApi.ap(params)
141 |
142 | expect(resData).toEqual(data)
143 | })
144 |
145 | test('array-data', async () => {
146 | const data = [0, 'array data']
147 | mock.onPost(reqOPUrl).reply(200, data)
148 | const resData = await fakePostApi.op({ param3: 'steve' })
149 |
150 | expect(resData).toEqual({ code: 0, data: 'array data' })
151 | })
152 |
153 | test('form-data', async () => {
154 | mock.onPost(reqOHUrl).reply(200, {})
155 | const formData = new FormData()
156 | formData.append('a', 'a')
157 | formData.append('b', '123')
158 |
159 | await fakePostApi.oh(formData)
160 |
161 | const {
162 | data,
163 | transformRequest,
164 | } = mock.history.post[0]
165 |
166 | expect(data).toBe(formData)
167 | expect(transformRequest).toBe(null)
168 | })
169 |
170 | test('custom-transformRequest', async () => {
171 | mock.onPost(reqCTUrl).reply(200, {})
172 |
173 | await fakePostApi.ct()
174 |
175 | const { data } = mock.history.post[0]
176 |
177 | expect(data).toBe('ct')
178 | })
179 |
180 | test('post-json', async () => {
181 | mock.onPost(reqPjUrl).reply(200, {})
182 |
183 | await fakePostApi.pj()
184 |
185 | const { data } = mock.history.post[0]
186 |
187 | expect(data).toBe(JSON.stringify(fakePostConfig.commonParams))
188 | expect(mock.history.post[0].headers['Content-Type']).toBe('application/json;charset=utf-8')
189 | })
190 |
191 | test('raw-data', async () => {
192 | const data = [0, 'array data']
193 | mock.onPost(reqRdUrl).reply(200, data)
194 | const resData = await fakePostApi.rd()
195 |
196 | expect(resData).toEqual(data)
197 | })
198 | })
199 |
--------------------------------------------------------------------------------
/test/__tests__/core.test.js:
--------------------------------------------------------------------------------
1 | import TuaApi from '@/index'
2 | import { ERROR_STRINGS } from '@/constants'
3 |
4 | describe('error handling', () => {
5 | const tuaApi = new TuaApi()
6 |
7 | test('non-function middleware', () => {
8 | // @ts-ignore
9 | expect(() => tuaApi.use('')).toThrow(TypeError(ERROR_STRINGS.middleware))
10 | })
11 |
12 | test('unknown reqType', () => {
13 | expect(() => new TuaApi({ reqType: '' })).toThrow(TypeError(ERROR_STRINGS.reqTypeFn('')))
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/test/__tests__/custom.test.js:
--------------------------------------------------------------------------------
1 | import TuaApi from '@/index'
2 | import { ERROR_STRINGS } from '@/constants'
3 |
4 | const customFetch = jest.fn(() => Promise.resolve({ data: Math.random() }))
5 |
6 | describe('customFetch', () => {
7 | const tuaApi = new TuaApi()
8 | const fooApi = tuaApi.getApi({
9 | prefix: 'foo',
10 | pathList: [
11 | { path: 'bar', customFetch },
12 | { path: 'axios', customFetch, reqType: 'axios' },
13 | { path: 'customAxios', customFetch, reqType: 'custom' },
14 | ],
15 | })
16 |
17 | test('both customFetch and reqType', () => {
18 | expect(() => new TuaApi({ reqType: 'axios', customFetch })).toThrow(TypeError(ERROR_STRINGS.reqTypeAndCustomFetch))
19 | })
20 |
21 | test('global customFetch should be called', async () => {
22 | const tuaApi = new TuaApi({ customFetch })
23 | const fooApi = tuaApi.getApi({
24 | prefix: 'foo',
25 | pathList: [
26 | { path: 'globalCustomFetch' },
27 | ],
28 | })
29 | await fooApi.globalCustomFetch()
30 |
31 | expect(customFetch).toBeCalled()
32 | })
33 |
34 | test('local customFetch should be called', async () => {
35 | await fooApi.bar()
36 |
37 | expect(customFetch).toBeCalled()
38 | })
39 |
40 | test('local customFetch should be called with reqType', async () => {
41 | await fooApi.axios()
42 |
43 | expect(customFetch).toBeCalled()
44 | })
45 |
46 | test('local customFetch should be called with `custom` reqType', async () => {
47 | await fooApi.customAxios()
48 |
49 | expect(customFetch).toBeCalled()
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/test/__tests__/exportUtils.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | getSyncFnMapByApis,
3 | getPreFetchFnKeysBySyncFnMap,
4 | } from '@/exportUtils'
5 |
6 | const noop1 = () => {}
7 | noop1.key = 'noop1'
8 | noop1.params = []
9 | const noop2 = () => {}
10 | noop2.key = 'noop2'
11 | noop2.params = {}
12 | const noop3 = () => {}
13 | noop3.key = 'noop3'
14 | noop3.params = { a: { required: true } }
15 | const noop4 = () => {}
16 | noop4.key = 'noop4'
17 | noop4.params = {}
18 |
19 | const syncFnMap = getSyncFnMapByApis({
20 | api1: { path1: noop1, path2: noop2 },
21 | api2: { path1: noop3, path2: noop4 },
22 | })
23 |
24 | test('getSyncFnMapByApis', () => {
25 | expect(syncFnMap).toEqual({ noop1, noop2, noop3, noop4 })
26 | })
27 |
28 | test('getPreFetchFnKeysBySyncFnMap', () => {
29 | expect(getPreFetchFnKeysBySyncFnMap(syncFnMap)).toEqual([
30 | { key: 'noop2' },
31 | { key: 'noop4' },
32 | ])
33 | })
34 |
--------------------------------------------------------------------------------
/test/__tests__/fn.test.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import MockAdapter from 'axios-mock-adapter'
3 |
4 | import fakeFnConfig from '@examples/apis-web/fake-fn'
5 | import { fakeFnApi } from '@examples/apis-web/'
6 |
7 | const mock = new MockAdapter(axios)
8 |
9 | const params = { param1: 'steve', param2: 'young' }
10 | const reqFPUrl = 'http://example-base.com/fake-fn/fn-params'
11 |
12 | describe('function params', () => {
13 | beforeEach(() => {
14 | // @ts-ignore
15 | mock.resetHistory()
16 | })
17 |
18 | test('should support function type params', async () => {
19 | Math.random = jest.fn(() => 'foo')
20 | mock.onPost(reqFPUrl).reply(200, {})
21 | await fakeFnApi.fp(params)
22 |
23 | const { data } = mock.history.post[0]
24 | expect(data).toEqual(JSON.stringify({
25 | ...fakeFnConfig.commonParams(params),
26 | t: 'foo',
27 | p1: params.param1,
28 | p2: params.param2,
29 | }))
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/test/__tests__/fp.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | map,
3 | pipe,
4 | join,
5 | merge,
6 | concat,
7 | filter,
8 | reduce,
9 | compose,
10 | flatten,
11 | mergeAll,
12 | } from '@/utils/'
13 |
14 | describe('functional programming functions', () => {
15 | test('map', () => {
16 | const fn1 = x => x
17 | const fn2 = x => x * 2
18 | const arr = [1, 2, 3]
19 | const obj = { a: 1, b: 2, c: 3 }
20 |
21 | expect(map(fn1)(arr)).toEqual(arr)
22 | expect(map(fn2)(obj)).toEqual({ a: 2, b: 4, c: 6 })
23 | })
24 |
25 | test('pipe', () => {
26 | const double = x => x * 2
27 | const square = x => x * x
28 |
29 | expect(pipe()(5)).toBe(5)
30 | expect(pipe(square)(5)).toBe(25)
31 | expect(pipe(square, double)(5)).toBe(50)
32 | expect(pipe(double, square, double)(5)).toBe(200)
33 | })
34 |
35 | test('join', () => {
36 | expect(join()([])).toBe('')
37 | expect(join()([1, 2])).toBe('1,2')
38 | expect(join('&')([])).toBe('')
39 | expect(join('&')([1, 2])).toBe('1&2')
40 | })
41 |
42 | test('merge', () => {
43 | const obj = { a: 'a', b: 'b' }
44 |
45 | expect(merge(obj, { c: 'c' })).toEqual({ a: 'a', b: 'b', c: 'c' })
46 | expect(merge(obj, { b: 'c' })).toEqual({ a: 'a', b: 'c' })
47 | })
48 |
49 | test('concat', () => {
50 | expect(concat([])([])).toEqual([])
51 | expect(concat([])([1, 2])).toEqual([1, 2])
52 | expect(concat([1, 2])([3, 4])).toEqual([3, 4, 1, 2])
53 | })
54 |
55 | test('reduce', () => {
56 | const fn = (x, y) => x + y
57 | const arr = [1, 2, 3]
58 |
59 | expect(reduce(fn)(arr)).toBe(6)
60 | expect(reduce(fn, 10)(arr)).toBe(16)
61 | expect(reduce(fn)([])).toBe(undefined)
62 | })
63 |
64 | test('filter', () => {
65 | const fn = x => x > 2
66 | const arr = [1, 2, 3]
67 |
68 | expect(filter(fn)(arr)).toEqual([3])
69 | })
70 |
71 | test('compose', () => {
72 | const double = x => x * 2
73 | const square = x => x * x
74 |
75 | expect(compose()(5)).toBe(5)
76 | expect(compose(square)(5)).toBe(25)
77 | expect(compose(square, double)(5)).toBe(100)
78 | expect(compose(double, square, double)(5)).toBe(200)
79 | })
80 |
81 | test('flatten', () => {
82 | const arr1 = [[1], [2], [3]]
83 | const arr2 = [[1], [2], [3, [4]]]
84 |
85 | expect(flatten(arr1)).toEqual([1, 2, 3])
86 | expect(flatten(arr2)).toEqual([1, 2, 3, 4])
87 | })
88 |
89 | test('mergeAll', () => {
90 | const arr = [
91 | { a: 'a' },
92 | { b: 'b' },
93 | { c: 'c' },
94 | ]
95 |
96 | expect(mergeAll(arr)).toEqual({
97 | a: 'a',
98 | b: 'b',
99 | c: 'c',
100 | })
101 | })
102 | })
103 |
--------------------------------------------------------------------------------
/test/__tests__/jsonp.test.js:
--------------------------------------------------------------------------------
1 | import { fakeGetApi } from '@examples/apis-web/'
2 | import fakeGetConfig from '@examples/apis-web/fake-get'
3 | import { ERROR_STRINGS } from '@/constants'
4 |
5 | jest.mock('fetch-jsonp')
6 |
7 | /**
8 | * @type {*}
9 | */
10 | const fetchJsonp = require('fetch-jsonp')
11 |
12 | const data = [0, 'array data']
13 | const returnVal = { code: 0, data: 'array data' }
14 |
15 | describe('mock data', () => {
16 | test('mock object data', async () => {
17 | fetchJsonp.mockResolvedValue({ json: () => data })
18 | /**
19 | * @type {*}
20 | */
21 | const resData = await fakeGetApi.mockObjectData()
22 |
23 | expect(resData.code).toBe(404)
24 | })
25 | })
26 |
27 | describe('fake jsonp requests', () => {
28 | test('jsonp options', async () => {
29 | const url = 'http://example-base.com/fake-get/jsonp-options'
30 | const jsonpOptions = {
31 | ...fakeGetConfig.jsonpOptions,
32 | charset: 'UTF-8',
33 | }
34 |
35 | await fakeGetApi.jsonpOptions()
36 | expect(fetchJsonp).toBeCalledWith(url, jsonpOptions)
37 |
38 | const callback = 'test_cb'
39 | const callbackName = 'test_cbName'
40 | await fakeGetApi.jsonpOptions(null, { callback, callbackName })
41 | expect(fetchJsonp).toBeCalledWith(url, {
42 | ...jsonpOptions,
43 | jsonpCallback: callback,
44 | jsonpCallbackFunction: callbackName,
45 | })
46 | })
47 |
48 | test('async-common-params', async () => {
49 | fetchJsonp.mockResolvedValue({ json: () => data })
50 | const resData = await fakeGetApi.acp()
51 |
52 | expect(resData).toEqual(returnVal)
53 | })
54 |
55 | test('array-data', async () => {
56 | fetchJsonp.mockResolvedValue({ json: () => data })
57 | const resData = await fakeGetApi.ap({})
58 |
59 | expect(resData).toEqual(returnVal)
60 | })
61 |
62 | test('object-params', async () => {
63 | fetchJsonp.mockResolvedValue({ json: () => data })
64 | const resData = await fakeGetApi.op({ param3: 'steve' })
65 |
66 | expect(resData).toEqual(returnVal)
67 | })
68 |
69 | test('invalid-req-type', () => {
70 | return expect(fakeGetApi.irt({ param3: 'steve' }))
71 | .rejects.toEqual(TypeError(ERROR_STRINGS.reqTypeFn('foobar')))
72 | })
73 |
74 | test('data should be passed through afterFn', async () => {
75 | fetchJsonp.mockResolvedValue({ json: () => data })
76 | const { afterData } = await fakeGetApi.afterData()
77 |
78 | expect(afterData).toBe('afterData')
79 | })
80 |
81 | test('there must be some data after afterFn', async () => {
82 | fetchJsonp.mockResolvedValue({ json: () => data })
83 | const resData = await fakeGetApi.noAfterData()
84 |
85 | expect(resData).toEqual(returnVal)
86 | })
87 | })
88 |
--------------------------------------------------------------------------------
/test/__tests__/utils.test.js:
--------------------------------------------------------------------------------
1 | import { ERROR_STRINGS } from '@/constants'
2 | import {
3 | combineUrls,
4 | promisifyWxApi,
5 | checkArrayParams,
6 | getDefaultParamObj,
7 | getParamStrFromObj,
8 | apiConfigToReqFnParams,
9 | } from '@/utils'
10 |
11 | test('combineUrls', () => {
12 | expect(combineUrls(0, 0)).toBe('0/0')
13 | expect(combineUrls(1, 1)).toBe('1/1')
14 | expect(combineUrls(1, null)).toBe('1')
15 | expect(combineUrls(null, 1)).toBe('/1')
16 | expect(combineUrls(undefined, undefined)).toBe('')
17 | expect(combineUrls(undefined, 'users')).toBe('/users')
18 | expect(combineUrls('https://api.github.com', undefined)).toBe('https://api.github.com')
19 | expect(combineUrls('https://api.github.com', 'users')).toBe('https://api.github.com/users')
20 | expect(combineUrls('https://api.github.com', '/users')).toBe('https://api.github.com/users')
21 | expect(combineUrls('https://api.github.com/', '/users')).toBe('https://api.github.com/users')
22 | expect(combineUrls('https://api.github.com/users', '')).toBe('https://api.github.com/users')
23 | expect(combineUrls('https://api.github.com/users', '/')).toBe('https://api.github.com/users/')
24 | })
25 |
26 | test('promisifyWxApi', () => {
27 | const fn = ({ success }) => setTimeout(() => success('test'), 0)
28 | const promisifiedFn = promisifyWxApi(fn)
29 |
30 | promisifiedFn().then(data => {
31 | expect(data).toBe('test')
32 | })
33 | })
34 |
35 | test('checkArrayParams', () => {
36 | expect(checkArrayParams({ params: {} })).toBe(true)
37 | expect(checkArrayParams({ args: { a: 'a' }, params: ['a'] })).toBe(true)
38 | expect(checkArrayParams({ args: {}, params: ['a'] })).toBe(false)
39 | })
40 |
41 | test('getDefaultParamObj', () => {
42 | expect(getDefaultParamObj({
43 | commonParams: { a: '1' },
44 | })).toEqual({ a: '1' })
45 |
46 | expect(getDefaultParamObj({
47 | args: { a: '1' },
48 | params: { b: '2' },
49 | })).toEqual({ b: '2' })
50 |
51 | expect(getDefaultParamObj({
52 | args: { a: '1' },
53 | params: { b: '2' },
54 | commonParams: { c: '3' },
55 | })).toEqual({ b: '2', c: '3' })
56 |
57 | expect(getDefaultParamObj({
58 | params: { a: { required: false } },
59 | })).toEqual({ a: '' })
60 |
61 | expect(() => getDefaultParamObj({
62 | params: { b: { required: true } },
63 | apiName: 'steve',
64 | })).toThrow(Error(ERROR_STRINGS.requiredParamFn('steve', 'b')))
65 |
66 | expect(() => getDefaultParamObj({
67 | params: { c: { isRequired: true } },
68 | apiName: 'steve',
69 | })).toThrow(Error(ERROR_STRINGS.requiredParamFn('steve', 'c')))
70 | })
71 |
72 | test('getParamStrFromObj', () => {
73 | expect(getParamStrFromObj()).toBe('')
74 | expect(getParamStrFromObj({})).toBe('')
75 | expect(getParamStrFromObj({ a: 1, b: 2 })).toBe('a=1&b=2')
76 | expect(getParamStrFromObj({ a: 1, b: 2, c: '哈喽' })).toBe('a=1&b=2&c=%E5%93%88%E5%96%BD')
77 | expect(getParamStrFromObj({ 哈喽: '哈喽' })).toBe('%E5%93%88%E5%96%BD=%E5%93%88%E5%96%BD')
78 | })
79 |
80 | test('apiConfigToReqFnParams', () => {
81 | expect(apiConfigToReqFnParams({
82 | pathList: [
83 | { path: 'api1', a: 'aa' },
84 | { path: 'api2', b: 'bb' },
85 | ],
86 | a: 'a',
87 | b: 'b',
88 | })).toEqual([
89 | { path: 'api1', a: 'aa', b: 'b' },
90 | { path: 'api2', a: 'a', b: 'bb' },
91 | ])
92 | })
93 |
--------------------------------------------------------------------------------
/test/__tests__/wx.test.js:
--------------------------------------------------------------------------------
1 | import '../__mocks__/wxMock'
2 | import TuaApi from '@/index'
3 | import { ERROR_STRINGS } from '@/constants'
4 |
5 | import fakeWx from '@examples/apis-mp/fake-wx'
6 | import { mockApi, fakeWxApi } from '@examples/apis-mp/'
7 |
8 | const testObjData = { code: 0, data: 'object data' }
9 | const testArrData = [0, 'array data']
10 |
11 | describe('mock data', () => {
12 | beforeEach(() => {
13 | wx.__TEST_DATA__ = {}
14 | })
15 |
16 | test('common mock data', async () => {
17 | wx.__TEST_DATA__ = { testData: testObjData }
18 | const resData = await mockApi.bar({ __mockData__: { code: 404, data: {} } })
19 |
20 | expect(resData.code).toBe(404)
21 | })
22 |
23 | test('self mock data', async () => {
24 | wx.__TEST_DATA__ = { testData: testObjData }
25 | const resData = await mockApi.foo({ __mockData__: { code: 404, data: {} } })
26 |
27 | expect(resData.code).toBe(500)
28 | })
29 |
30 | test('null mock data', async () => {
31 | wx.__TEST_DATA__ = { testData: testObjData }
32 | const resData = await mockApi.null({ __mockData__: { code: 404, data: {} } })
33 |
34 | expect(resData).toEqual({ code: 0, data: 'object data' })
35 | })
36 |
37 | test('dynamic object mock data', async () => {
38 | wx.__TEST_DATA__ = { testData: testObjData }
39 | mockApi.null.mock = { code: 123 }
40 | const resData = await mockApi.null()
41 |
42 | expect(resData.code).toBe(123)
43 | })
44 |
45 | test('dynamic function mock data', async () => {
46 | wx.__TEST_DATA__ = { testData: testObjData }
47 | mockApi.foo.mock = ({ mockCode }) => ({ code: mockCode })
48 | const resData = await mockApi.foo({ mockCode: 123 })
49 |
50 | expect(resData.code).toBe(123)
51 | })
52 | })
53 |
54 | describe('middleware', () => {
55 | const tuaApi = new TuaApi()
56 | const fakeWxApi = tuaApi.getApi(fakeWx)
57 | const globalMiddlewareFn = jest.fn(async (ctx, next) => {
58 | expect(ctx.req.host).toBeDefined()
59 | expect(ctx.req.baseUrl).toBeDefined()
60 | expect(ctx.req.type).toBeDefined()
61 | expect(ctx.req.method).toBeDefined()
62 | expect(ctx.req.path).toBeDefined()
63 | expect(ctx.req.prefix).toBeDefined()
64 | expect(ctx.req.reqType).toBeDefined()
65 | expect(ctx.req.reqParams).toBeDefined()
66 | expect(ctx.req.axiosOptions).toBeDefined()
67 | expect(ctx.req.jsonpOptions).toBeDefined()
68 | expect(ctx.req.reqFnParams).toBeDefined()
69 |
70 | expect(ctx.req.callbackName).toBeUndefined()
71 |
72 | await next()
73 |
74 | expect(ctx.reqTime).toBeDefined()
75 | expect(ctx.startTime).toBeDefined()
76 | expect(ctx.endTime).toBeDefined()
77 |
78 | expect(ctx.res.data).toBeDefined()
79 | expect(ctx.res.rawData).toBeDefined()
80 | })
81 |
82 | tuaApi.use(globalMiddlewareFn)
83 |
84 | beforeEach(() => {
85 | wx.__TEST_DATA__ = { testData: {} }
86 | })
87 |
88 | test('useGlobalMiddleware', async () => {
89 | await fakeWxApi.arrayData()
90 | expect(globalMiddlewareFn).toBeCalledTimes(0)
91 | await fakeWxApi.fail()
92 | expect(globalMiddlewareFn).toBeCalledTimes(1)
93 | })
94 | })
95 |
96 | describe('fake wx requests', () => {
97 | beforeEach(() => {
98 | wx.__TEST_DATA__ = {}
99 | })
100 |
101 | test('same key', () => {
102 | expect(fakeWxApi.fail.key).not.toEqual(fakeWxApi.anotherFail.key)
103 | })
104 |
105 | test('object-data', async () => {
106 | wx.__TEST_DATA__ = { testData: testObjData }
107 | const resData = await fakeWxApi.objectData({ param3: '123' })
108 |
109 | expect(resData).toEqual({ code: 0, data: 'object data' })
110 | })
111 |
112 | test('array-data', async () => {
113 | wx.__TEST_DATA__ = { testData: testArrData }
114 | const resData = await fakeWxApi.arrayData(null)
115 |
116 | expect(resData).toEqual({ code: 0, data: 'array data' })
117 | })
118 |
119 | test('fail', () => {
120 | wx.__TEST_DATA__ = { isTestFail: true }
121 |
122 | return expect(fakeWxApi.fail({ a: 'b' }))
123 | .rejects.toEqual(Error('test'))
124 | })
125 |
126 | test('no-beforeFn', () => {
127 | return expect(fakeWxApi.noBeforeFn())
128 | .rejects.toEqual(Error(ERROR_STRINGS.noData))
129 | })
130 |
131 | test('hide-loading', async () => {
132 | wx.showLoading.mockClear()
133 | wx.__TEST_DATA__ = { testData: testObjData }
134 | const resData = await fakeWxApi.hideLoading()
135 |
136 | expect(resData).toEqual({ code: 0, data: 'object data' })
137 | expect(wx.showLoading).toHaveBeenCalledTimes(0)
138 | })
139 |
140 | test('type-get', async () => {
141 | wx.showLoading.mockClear()
142 | wx.__TEST_DATA__ = { testData: testObjData }
143 | await fakeWxApi.typeGet()
144 | const [[{ method }]] = wx.request.mock.calls
145 |
146 | expect(method).toBe('GET')
147 | expect(wx.showLoading).toHaveBeenCalledTimes(1)
148 | })
149 |
150 | test('unknown-type', () => {
151 | return expect(fakeWxApi.unknownType())
152 | .rejects.toEqual(Error(ERROR_STRINGS.unknownMethodFn('FOO')))
153 | })
154 |
155 | test('nav-loading', async () => {
156 | wx.showNavigationBarLoading.mockClear()
157 | wx.__TEST_DATA__ = { testData: testObjData }
158 | const resData = await fakeWxApi.navLoading()
159 |
160 | expect(resData).toEqual({ code: 0, data: 'object data' })
161 | expect(wx.showNavigationBarLoading).toHaveBeenCalledTimes(1)
162 | })
163 | })
164 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "checkJs": true,
5 | "baseUrl": ".",
6 | "target": "es5",
7 | "resolveJsonModule": true,
8 | "experimentalDecorators": true,
9 | "lib": [
10 | "dom",
11 | "es2015"
12 | ],
13 | "paths":{
14 | "@/*": ["src/*"],
15 | "@examples/*": ["examples/*"]
16 | }
17 | },
18 | "exclude": ["node_modules", "dist"]
19 | }
20 |
--------------------------------------------------------------------------------