复制浏览器代码
parent
7d68bd92ff
commit
daabe8861c
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
|
||||||
|
._*
|
||||||
|
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
|
@ -0,0 +1,10 @@
|
||||||
|
jsxBracketSameLine: true
|
||||||
|
jsxSingleQuote: true
|
||||||
|
semi: false
|
||||||
|
singleQuote: true
|
||||||
|
printWidth: 80
|
||||||
|
useTabs: false
|
||||||
|
tabWidth: 2
|
||||||
|
trailingComma: none
|
||||||
|
bracketSpacing: true
|
||||||
|
arrowParens: avoid
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,83 @@
|
||||||
|
## node-fetch的增强版
|
||||||
|
> node-fetch的增强版, 增加注入及数据处理, 支持多实例。
|
||||||
|
|
||||||
|
|
||||||
|
## Node.js 兼容性
|
||||||
|
|
||||||
|
因为需要支持 ESM。所以需要 Node.js >= v12.0.0,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```js
|
||||||
|
import fetch from '@bytedo/node-fetch'
|
||||||
|
|
||||||
|
|
||||||
|
fetch('/get_list', {body: {page: 1}})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(list => {
|
||||||
|
console.log(list)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 创建一个新的fetch实例, 可传入新的基础域名, 和公共参数等
|
||||||
|
var f1 = fetch.create('//192.168.1.101', {headers: {token: 123456}})
|
||||||
|
|
||||||
|
f1('/get_list', {body: {page: 1}})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(list => {
|
||||||
|
console.log(list)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### APIs
|
||||||
|
|
||||||
|
#### 1. fetch(url[, options<Object>])
|
||||||
|
> 发起一个网络请求, options的参数如下。 同时支持配置公共域名, 公共参数。
|
||||||
|
|
||||||
|
+ method`<String>` 默认GET, 可选GET/POST/PUT/DELETE...
|
||||||
|
+ body`<Any>` 要发送的数据, 如果是不允许有`body`的方式, 会被自动拼接到url上
|
||||||
|
+ cache`<String>` 是否缓存,
|
||||||
|
+ credentials`<String/Boolean>` 是否校验
|
||||||
|
+ signal`<Object>` 网络控制信号, 可用于中断请求
|
||||||
|
+ timeout`<Number>` 超时时间, 默认30秒, 单位毫秒
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch.BASE_URL = '//192.168.1.100'
|
||||||
|
// 1.2.0开始支持注入
|
||||||
|
fetch.inject.request(function(conf) {
|
||||||
|
// 无需返回值, 但需要注意这是引用类型,不要对带个conf赋值
|
||||||
|
conf.headers.token = 123456
|
||||||
|
})
|
||||||
|
|
||||||
|
// 响应注入, 需要有返回值
|
||||||
|
fetch.inject.response(function(res) {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### 2. fetch.create()
|
||||||
|
> 创建一个新的fetch实例, 可以无限创建多个实例(用于同一个项目中有多组不同的接口)。
|
||||||
|
|
||||||
|
```js
|
||||||
|
var another = fetch.create()
|
||||||
|
another.BASE_URL = '//192.168.1.101'
|
||||||
|
// 新创建的实例, 也支持注入
|
||||||
|
another.inject.request(function(conf) {
|
||||||
|
conf.headers.token = 123456
|
||||||
|
})
|
||||||
|
|
||||||
|
another.inject.response(function(res) {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* {build}
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2021/08/09 11:59:41
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Es from 'esbuild'
|
||||||
|
|
||||||
|
const mode = process.argv.slice(2).shift()
|
||||||
|
|
||||||
|
Es.build({
|
||||||
|
entryPoints: ['src/index.js'],
|
||||||
|
bundle: true,
|
||||||
|
watch: mode === 'dev' ? true : false,
|
||||||
|
minify: mode === 'dev' ? false : true,
|
||||||
|
format: 'esm',
|
||||||
|
outdir: 'dist'
|
||||||
|
})
|
13
package.json
13
package.json
|
@ -1,10 +1,12 @@
|
||||||
{
|
{
|
||||||
|
"type": "module",
|
||||||
"name": "@bytedo/node-fetch",
|
"name": "@bytedo/node-fetch",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "node-fetch的增强版, 增加注入及数据处理",
|
||||||
"main": "index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"start": "node build.js dev",
|
||||||
|
"build": "node build.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -15,5 +17,8 @@
|
||||||
"fetch"
|
"fetch"
|
||||||
],
|
],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^3.3.0"
|
||||||
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
/**
|
||||||
|
* 新一代版本
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2020/07/31 18:59:47
|
||||||
|
*/
|
||||||
|
|
||||||
|
import nativeFetch from 'node-fetch'
|
||||||
|
import { Format, toS } from './lib/format.js'
|
||||||
|
|
||||||
|
const NOBODY_METHODS = ['GET', 'HEAD']
|
||||||
|
const FORM_TYPES = {
|
||||||
|
form: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
json: 'application/json; charset=UTF-8',
|
||||||
|
text: 'text/plain; charset=UTF-8'
|
||||||
|
}
|
||||||
|
const ERRORS = {
|
||||||
|
10001: 'Argument url is required',
|
||||||
|
10012: 'Parse error',
|
||||||
|
10100: 'Request canceled',
|
||||||
|
10104: 'Request pending...',
|
||||||
|
10200: 'Ok',
|
||||||
|
10204: 'No content',
|
||||||
|
10304: 'Not modified',
|
||||||
|
10500: 'Internal Server Error',
|
||||||
|
10504: 'Connected timeout'
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Request {
|
||||||
|
constructor(url = '', options = {}, owner) {
|
||||||
|
if (!url) {
|
||||||
|
throw new Error(ERRORS[10001])
|
||||||
|
}
|
||||||
|
|
||||||
|
// url规范化
|
||||||
|
url = url.replace(/#.*$/, '')
|
||||||
|
|
||||||
|
if (owner.BASE_URL) {
|
||||||
|
if (!/^([a-z]+:|\/\/)/.test(url)) {
|
||||||
|
url = owner.BASE_URL + url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.method = (options.method || 'get').toUpperCase()
|
||||||
|
|
||||||
|
this._owner = owner
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
headers: {
|
||||||
|
'content-type': FORM_TYPES.form
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
cache: 'default',
|
||||||
|
signal: null, // 超时信号, 配置该项时, timeout不再生效
|
||||||
|
timeout: 30000 // 超时时间, 单位毫秒, 默认30秒
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.signal) {
|
||||||
|
this.control = new AbortController()
|
||||||
|
options.signal = this.control.signal
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.headers) {
|
||||||
|
let headers = this.options.headers
|
||||||
|
Object.assign(headers, options.headers)
|
||||||
|
options.headers = headers
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this.options, options, { url })
|
||||||
|
|
||||||
|
if (owner._inject_req) {
|
||||||
|
owner._inject_req(this.options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.__next__()
|
||||||
|
}
|
||||||
|
|
||||||
|
__next__() {
|
||||||
|
var options = this.options
|
||||||
|
var hasAttach = false // 是否有附件
|
||||||
|
var noBody = NOBODY_METHODS.includes(options.method)
|
||||||
|
|
||||||
|
/* -------------------------- 1»» 请求的内容 --------------------- */
|
||||||
|
if (options.body) {
|
||||||
|
var type = typeof options.body
|
||||||
|
switch (type) {
|
||||||
|
case 'number':
|
||||||
|
case 'string':
|
||||||
|
this.__type__('text')
|
||||||
|
break
|
||||||
|
case 'object':
|
||||||
|
// 如果是一个 FormData对象,且为不允许携带body的方法,则直接改为POST
|
||||||
|
if (options.body.constructor === FormData) {
|
||||||
|
hasAttach = true
|
||||||
|
// 修正请求类型
|
||||||
|
if (noBody) {
|
||||||
|
options.method = 'POST'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let k in options.body) {
|
||||||
|
if (toS.call(options.body[k]) === '[object File]') {
|
||||||
|
hasAttach = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 有附件,则改为FormData
|
||||||
|
if (hasAttach) {
|
||||||
|
if (noBody) {
|
||||||
|
options.method = 'POST'
|
||||||
|
}
|
||||||
|
options.body = Format.mkFormData(options.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasAttach) {
|
||||||
|
delete options.headers['content-type']
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------- 2»» 处理跨域 --------------------- */
|
||||||
|
|
||||||
|
/* ------------- 3»» 根据method类型, 处理表单数据 ---------------- */
|
||||||
|
|
||||||
|
// 拼接到url上
|
||||||
|
if (noBody) {
|
||||||
|
let tmp = Format.param(options.body)
|
||||||
|
if (tmp) {
|
||||||
|
options.url += (~options.url.indexOf('?') ? '&' : '?') + tmp
|
||||||
|
}
|
||||||
|
delete options.body
|
||||||
|
} else {
|
||||||
|
if (!hasAttach) {
|
||||||
|
if (~options.headers['content-type'].indexOf('json')) {
|
||||||
|
options.body = JSON.stringify(options.body)
|
||||||
|
} else {
|
||||||
|
options.body = Format.param(options.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------- 4»» 超时处理 -----------------------*/
|
||||||
|
if (options.timeout && options.timeout > 0) {
|
||||||
|
this.timer = setTimeout(_ => {
|
||||||
|
this.abort()
|
||||||
|
}, options.timeout)
|
||||||
|
|
||||||
|
delete options.timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------- 5»» 构造请求 ------------------- */
|
||||||
|
var url = options.url
|
||||||
|
delete options.url
|
||||||
|
for (let k in options) {
|
||||||
|
if (
|
||||||
|
options[k] === null ||
|
||||||
|
options[k] === undefined ||
|
||||||
|
options[k] === ''
|
||||||
|
) {
|
||||||
|
delete options[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nativeFetch(url, options)
|
||||||
|
.then(r => {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
let isSucc = r.status >= 200 && r.status < 400
|
||||||
|
let _type
|
||||||
|
if (this._owner._inject_res) {
|
||||||
|
r = this._owner._inject_res(r)
|
||||||
|
_type = toS.call(r)
|
||||||
|
}
|
||||||
|
if (isSucc) {
|
||||||
|
return r
|
||||||
|
} else {
|
||||||
|
if (_type === '[object Promise]') {
|
||||||
|
return r.then(_ => Promise.reject(_))
|
||||||
|
} else {
|
||||||
|
return Promise.reject(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
return Promise.reject(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
this.control.abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
__type__(type) {
|
||||||
|
this.options.headers['content-type'] = FORM_TYPES[type]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inject(target) {
|
||||||
|
target.inject = {
|
||||||
|
request(callback) {
|
||||||
|
target._inject_req = callback
|
||||||
|
},
|
||||||
|
response(callback) {
|
||||||
|
target._inject_res = callback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetch = function (url, options) {
|
||||||
|
return new _Request(url, options, fetch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch.create = function () {
|
||||||
|
var another = function (url, options) {
|
||||||
|
return new _Request(url, options, another)
|
||||||
|
}
|
||||||
|
inject(another)
|
||||||
|
return another
|
||||||
|
}
|
||||||
|
|
||||||
|
inject(fetch)
|
||||||
|
|
||||||
|
export default fetch
|
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @authors yutent (yutent.io@gmail.com)
|
||||||
|
* @date 2016-11-26 16:35:45
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const toS = Object.prototype.toString
|
||||||
|
export const encode = encodeURIComponent
|
||||||
|
export const decode = decodeURIComponent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单序列化
|
||||||
|
*/
|
||||||
|
function serialize(p, obj, query) {
|
||||||
|
var k
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
obj.forEach(function (it, i) {
|
||||||
|
k = p ? `${p}[${Array.isArray(it) ? i : ''}]` : i
|
||||||
|
if (typeof it === 'object') {
|
||||||
|
serialize(k, it, query)
|
||||||
|
} else {
|
||||||
|
query(k, it)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for (let i in obj) {
|
||||||
|
k = p ? `${p}[${i}]` : i
|
||||||
|
if (typeof obj[i] === 'object') {
|
||||||
|
serialize(k, obj[i], query)
|
||||||
|
} else {
|
||||||
|
query(k, obj[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Format = {
|
||||||
|
mkFormData(data) {
|
||||||
|
let form = new FormData()
|
||||||
|
for (let i in data) {
|
||||||
|
let el = data[i]
|
||||||
|
if (Array.isArray(el)) {
|
||||||
|
el.forEach(function (it) {
|
||||||
|
form.append(i + '[]', it)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
form.append(i, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return form
|
||||||
|
},
|
||||||
|
param(obj) {
|
||||||
|
if (!obj || typeof obj === 'string' || typeof obj === 'number') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
let arr = []
|
||||||
|
let query = function (k, v) {
|
||||||
|
if (/native code/.test(v)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v = typeof v === 'function' ? v() : v
|
||||||
|
v = toS.call(v) === '[object File]' ? v : encode(v)
|
||||||
|
|
||||||
|
arr.push(encode(k) + '=' + v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
serialize('', obj, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr.join('&')
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue