+5
-0
| # 版本更新说明 | ||
| ## v0.4.0 - 2021.05.07 | ||
| 1. 新增配置项 `retry`,引入重试机制 | ||
| 2. 重构了调试日志功能 | ||
| ## v0.3.0 - 2021.05.06 | ||
@@ -4,0 +9,0 @@ |
@@ -91,2 +91,10 @@ 'use strict' | ||
| /** | ||
| * 重试次数 | ||
| * | ||
| * 说明: | ||
| * 1. `0` 表示不重试 | ||
| */ | ||
| retry: 0, | ||
| /** | ||
| * 是否开启调试模式 | ||
@@ -101,4 +109,19 @@ * | ||
| * 用于打印日志的函数 | ||
| * @param {object} content 日志内容对象 | ||
| * @param {number} content.status 响应状态码 | ||
| * @param {string} content.url 请求地址 | ||
| * @param {object} content.config 配置项 | ||
| * @param {number} content.startTime 发起请求任务时间(时间戳) | ||
| * @param {number} content.endTime 结束请求任务时间(时间戳) | ||
| * @param {number} content.requestTime 本次发起请求时间(时间戳) | ||
| * @param {number} content.responseTime 本次结束响应时间(时间戳) | ||
| * @param {string} content.startTimeText 发起请求任务时间(文本格式) | ||
| * @param {string} content.endTimeText 结束请求任务时间(文本格式) | ||
| * @param {string} content.requestTimeText 本次发起请求时间(文本格式) | ||
| * @param {string} content.responseTimeText 本次结束响应时间(文本格式) | ||
| */ | ||
| logging: console.log, | ||
| logging: function logging(content) { | ||
| const message = `${content.startTimeText} ${content.responseTime - content.requestTime}ms ${content.config.method} ${content.url} ${content.status}` | ||
| console.log(message) | ||
| }, | ||
@@ -105,0 +128,0 @@ /*********************************** 对象类配置 **********************************/ |
@@ -29,2 +29,3 @@ 'use strict' | ||
| 'timeout', | ||
| 'retry', | ||
| 'mock', | ||
@@ -35,2 +36,3 @@ 'adapter', | ||
| 'responseItems', | ||
| 'validateStatus', | ||
| 'sign', | ||
@@ -37,0 +39,0 @@ 'debug', |
@@ -159,2 +159,14 @@ 'use strict' | ||
| /** | ||
| * 配置项 `retry` | ||
| * | ||
| * 规则: | ||
| * 1. 非负整数 | ||
| */ | ||
| if (config.retry !== undefined) { | ||
| if (!(Number.isInteger(config.retry) && config.retry >= 0)) { | ||
| throw new Error('配置项 `retry` 要求是一个非负整数(0,1,2,...)') | ||
| } | ||
| } | ||
| /** | ||
| * 配置项 `headers` | ||
@@ -161,0 +173,0 @@ * |
@@ -101,10 +101,10 @@ 'use strict' | ||
| /** | ||
| * 发起请求 | ||
| * @param {object} config 配置项 | ||
| * 发起请求(包含重试逻辑) | ||
| */ | ||
| async request(...args) { | ||
| // 整理参数,生成标准格式的 `config` | ||
| const customConfig = buildConfig(...args) | ||
| const config = mergeConfig(baseConfig, this.defaults, normalizeConfig(customConfig)) | ||
| const context = new Context(config) | ||
| // 加入 `签名中间件` | ||
| const signFn = this.getSignMiddleware(config) | ||
@@ -115,2 +115,49 @@ if (signFn) { | ||
| /** 最大重试次数 */ | ||
| let retry = config.retry | ||
| /** 已重试次数 */ | ||
| let retries = -1 | ||
| /** 检验成功请求函数 */ | ||
| const validateStatus = config.validateStatus | ||
| /** 响应结果列表 */ | ||
| const responses = [] | ||
| while (retry >= 0) { | ||
| retry-- | ||
| retries++ | ||
| const result = await this.execSingleRequest(config, retries) | ||
| responses.push(result) | ||
| if (validateStatus(result.status)) { | ||
| // 返回响应结果 | ||
| const { responseItems } = config | ||
| const output = {} | ||
| responseItems.forEach((key) => { | ||
| if (typeof key === 'string') { | ||
| output[key] = result[key] | ||
| } else if (typeof key === 'object') { | ||
| const { item, alias } = key | ||
| output[alias] = result[item] | ||
| } else { | ||
| throw new Error('配置项 `responseItems` 格式错误') | ||
| } | ||
| }) | ||
| if (responseItems.includes('responses')) { | ||
| output.responses = responses | ||
| } | ||
| return output | ||
| } | ||
| } | ||
| throw new Error(responses[responses.length - 1]) | ||
| } | ||
| /** | ||
| * 执行单次请求 | ||
| */ | ||
| async execSingleRequest(config, retries) { | ||
| const context = new Context(config) | ||
| context.retries = retries | ||
| const fn = this.createPipeline(config.middleware) | ||
@@ -117,0 +164,0 @@ return await fn(context) |
+77
-0
@@ -5,2 +5,3 @@ 'use strict' | ||
| const mockAdapter = require('../adapters/mock.js') | ||
| const STATE = Symbol('context#state') | ||
@@ -14,5 +15,49 @@ module.exports = class Context { | ||
| this.adapter = config.mock && typeof config.mock === 'object' ? mockAdapter : config.adapter | ||
| /** 公共存储空间 */ | ||
| this[STATE] = {} | ||
| } | ||
| /** | ||
| * 以指定名称存储当前时间 | ||
| * @param {string} name 名称 | ||
| */ | ||
| setTime(name) { | ||
| /** | ||
| * 辅助函数:对数字补零到指定位数 | ||
| * @param {number} number 被格式化的数字 | ||
| * @param {number} digit 格式化的位数 | ||
| * @returns {string} | ||
| */ | ||
| function zerofill(number, digit = 2) { | ||
| const zero = '0' | ||
| for (let i = 0; i < digit; i++) { | ||
| if (number < Math.pow(10, i + 1)) { | ||
| const str = zero.repeat(digit - i - 1) + number.toString() | ||
| return str | ||
| } | ||
| } | ||
| return number | ||
| } | ||
| const time = new Date() | ||
| const year = time.getFullYear() | ||
| const month = zerofill(time.getMonth()) | ||
| const day = zerofill(time.getDate()) | ||
| const hour = zerofill(time.getHours()) | ||
| const minute = zerofill(time.getMinutes()) | ||
| const second = zerofill(time.getSeconds()) | ||
| const millisecond = zerofill(time.getMilliseconds(), 3) | ||
| const str = `${year}-${month}-${day} ${hour}:${minute}:${second}.${millisecond}` | ||
| this[STATE][name] = time.getTime() | ||
| this[STATE][name + 'Text'] = str | ||
| } | ||
| get state() { | ||
| return this[STATE] | ||
| } | ||
| /** | ||
| * 用于在中间件中快速获取 `url` | ||
@@ -75,2 +120,34 @@ */ | ||
| } | ||
| /** | ||
| * 合成打印日志所需的内容 | ||
| * @returns {object} content 日志内容对象 | ||
| * @returns {number} content.status 响应状态码 | ||
| * @returns {string} content.url 请求地址 | ||
| * @returns {object} content.config 配置项 | ||
| * @returns {number} content.startTime 发起请求任务时间(时间戳) | ||
| * @returns {number} content.endTime 结束请求任务时间(时间戳) | ||
| * @returns {number} content.requestTime 本次发起请求时间(时间戳) | ||
| * @returns {number} content.responseTime 本次结束响应时间(时间戳) | ||
| * @returns {string} content.startTimeText 发起请求任务时间(文本格式) | ||
| * @returns {string} content.endTimeText 结束请求任务时间(文本格式) | ||
| * @returns {string} content.requestTimeText 本次发起请求时间(文本格式) | ||
| * @returns {string} content.responseTimeText 本次结束响应时间(文本格式) | ||
| */ | ||
| getLoggingContent() { | ||
| const ctx = this | ||
| return { | ||
| status: ctx.response.status, | ||
| url: ctx.url, | ||
| config: ctx.config, | ||
| startTime: ctx.state.startTime, | ||
| endTime: ctx.state.endTime, | ||
| requestTime: ctx.state.requestTime, | ||
| responseTime: ctx.state.responseTime, | ||
| startTimeText: ctx.state.startTimeText, | ||
| endTimeText: ctx.state.endTimeText, | ||
| requestTimeText: ctx.state.requestTimeText, | ||
| responseTimeText: ctx.state.responseTimeText, | ||
| } | ||
| } | ||
| } |
@@ -9,3 +9,10 @@ 'use strict' | ||
| module.exports = async function dispatch(ctx) { | ||
| // 记录发起请求时间 | ||
| ctx.setTime('requestTime') | ||
| const result = await ctx.adapter(ctx.config) | ||
| // 记录完成请求时间 | ||
| ctx.setTime('responseTime') | ||
| ctx.response = new Response(result) | ||
@@ -12,0 +19,0 @@ ctx.request = result.request |
@@ -7,29 +7,26 @@ 'use strict' | ||
| module.exports = async function initialize(ctx, next) { | ||
| /** 请求开始时间 */ | ||
| const startTime = Date.now() | ||
| // 记录本次请求任务开始时间 | ||
| ctx.setTime('startTime') | ||
| await next() | ||
| /** 请求结束时间 */ | ||
| const endTime = Date.now() | ||
| const cost = endTime - startTime | ||
| // 记录本次请求任务结束时间 | ||
| ctx.setTime('endTime') | ||
| // 打印日志 | ||
| if (ctx.config.debug && typeof ctx.config.logging === 'function') { | ||
| const message = `[${ctx.response.status}] ${ctx.config.method} ${ctx.url} ${cost}ms ` | ||
| ctx.config.logging(message) | ||
| const content = ctx.getLoggingContent() | ||
| ctx.config.logging(content) | ||
| } | ||
| // 返回响应结果 | ||
| const { responseItems } = ctx.config | ||
| const output = {} | ||
| responseItems.forEach((key) => { | ||
| if (typeof key === 'string') { | ||
| output[key] = ctx.response[key] | ||
| } else if (typeof key === 'object') { | ||
| const { item, alias } = key | ||
| output[alias] = ctx.response[item] | ||
| } else { | ||
| throw new Error('配置项 `responseItems` 格式错误') | ||
| } | ||
| }) | ||
| return output | ||
| return { | ||
| status: ctx.response.status, | ||
| statusText: ctx.response.statusText, | ||
| headers: ctx.response.headers, | ||
| data: ctx.response.data, | ||
| request: ctx.response.request, | ||
| url: ctx.url, | ||
| config: ctx.config, | ||
| } | ||
| } |
+1
-1
| { | ||
| "name": "jshttp", | ||
| "version": "0.3.0", | ||
| "version": "0.4.0", | ||
| "description": "能在任何 Javascript 环境使用的 HTTP 请求库,支持 Node.js,浏览器,微信小程序,支付宝小程序,百度小程序 ...", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+7
-0
@@ -205,2 +205,9 @@ # jshttp | ||
| /** | ||
| * 重试次数 | ||
| * | ||
| * 使用 `validateStatus` 方法对响应结果进行校验,校验不通过则重试。建议配置中间件的 `ctx.retries` (当前重试次数)属性操作,当其大于 0 时,进行若干操作。 | ||
| */ | ||
| retry: 0, | ||
| /****************** 以下配置项一般情况下不需要额外设置,使用默认值即可 *********************/ | ||
@@ -207,0 +214,0 @@ |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
119415
5.2%1721
9.2%487
1.46%