Skip to content

Commit c74bf37

Browse files
committed
(wip)feat: add retry
- todo: mini program and harmony
1 parent 97d5879 commit c74bf37

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2211
-308
lines changed

packages/browser/src/cache/index.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as common from '../@internal'
2+
3+
export class LocalStorageCacheManager<T> implements common.cache.PersistentCacheManager<T> {
4+
persistPath: string;
5+
6+
constructor(persistLocation: string) {
7+
this.persistPath = persistLocation
8+
}
9+
10+
async get(key: string): Promise<T | null> {
11+
const itemKey = this.getPersistKey(key)
12+
const raw = localStorage.getItem(itemKey)
13+
if (!raw) {
14+
return null
15+
}
16+
return this.parse(raw)
17+
}
18+
19+
async set(key: string, val: T): Promise<void> {
20+
const itemKey = this.getPersistKey(key)
21+
const raw = this.stringify(val)
22+
localStorage.setItem(itemKey, raw)
23+
}
24+
25+
async delete(key: string): Promise<void> {
26+
const itemKey = this.getPersistKey(key)
27+
localStorage.removeItem(itemKey)
28+
}
29+
30+
private getPersistKey(key: string): string {
31+
return `qnsdk:${this.persistPath}:${key}`
32+
}
33+
34+
private parse(val: string): T {
35+
return JSON.parse(val)
36+
}
37+
38+
private stringify(val: T): string {
39+
return JSON.stringify(val)
40+
}
41+
}

packages/browser/src/http/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,17 @@ export class HttpClient implements common.HttpClient {
9696

9797
mockProgress?.end()
9898

99+
const reqId = xhr.getResponseHeader('x-reqId')
100+
if (!reqId) {
101+
resolve({
102+
error: new UploadError('HijackedError', 'Response header x-reqId not found')
103+
})
104+
return
105+
}
106+
99107
resolve({
100108
result: {
101-
reqId: xhr.getResponseHeader('x-reqId') || undefined,
109+
reqId,
102110
code: xhr.status,
103111
data: xhr.responseText
104112
}

packages/common/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
"build": "run-p build:*",
1010
"dev": "run-p \"build:* -- -w\"",
1111
"postinstall": "run-p build:*",
12-
"build:browser": "cpx \"src/**/*\" ../browser/src/@internal",
13-
"build:wechat-miniprogram": "cpx \"src/**/*\" ../wechat-miniprogram/src/@internal",
14-
"build:harmony": "cpx \"src/**/*\" ../harmony/library/src/main/ets/components/@internal"
12+
"cleanup": "rm -fr ../browser/src/@internal ../wechat-miniprogram/src/@internal ../harmony/library/src/main/ets/components/@internal",
13+
"build:browser": "cpx \"src/**/!(*.test,*.mock).ts\" ../browser/src/@internal",
14+
"build:wechat-miniprogram": "cpx \"src/**/!(*.test,*.mock).ts\" ../wechat-miniprogram/src/@internal",
15+
"build:harmony": "cpx \"src/**/!(*.test,*.mock).ts\" ../harmony/library/src/main/ets/components/@internal"
1516
},
1617
"author": "",
1718
"license": "ISC"

packages/common/src/api/index.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -431,17 +431,20 @@ export class UploadApis {
431431
}
432432

433433
interface GetHostConfigParams {
434+
// TODO: typo
434435
assessKey: string
435436
bucket: string
437+
serverUrl?: string
436438
}
437439

438-
interface HostConfig {
440+
export interface HostConfig {
439441
hosts: Array<{
440442
region: string
441443
ttl: number
442444
up: {
443445
domains: string[]
444446
old: string[]
447+
acc_domains?: string[]
445448
}
446449
io: {
447450
domains: string[]
@@ -469,8 +472,8 @@ export class ConfigApis {
469472
async getHostConfig(params: GetHostConfigParams): Promise<Result<HostConfig>> {
470473
/** 从配置中心获取上传服务地址 */
471474
const query = `ak=${encodeURIComponent(params.assessKey)}&bucket=${encodeURIComponent(params.bucket)}`
472-
// TODO: 支持设置,私有云自动获取上传地址
473-
const url = `${this.serverUrl}/v4/query?${query}`
475+
const serverUrl = params.serverUrl || this.serverUrl
476+
const url = `${serverUrl}/v4/query?${query}`
474477
const response = await this.httpClient.get(url)
475478

476479
if (!isSuccessResult(response)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { MemoryCacheManager, PersistentNever } from './index'
2+
3+
interface CacheData {
4+
result: string
5+
}
6+
7+
describe('CacheManager', () => {
8+
test('test MemoryCacheManager', async () => {
9+
const memoryCacheManager = new MemoryCacheManager<CacheData>()
10+
11+
await memoryCacheManager.set('key', {
12+
result: 'val'
13+
})
14+
let val = await memoryCacheManager.get('key')
15+
expect(val).toEqual({
16+
result: 'val'
17+
})
18+
19+
await memoryCacheManager.delete('key')
20+
val = await memoryCacheManager.get('key')
21+
expect(val).toBe(null)
22+
})
23+
24+
test('test PersistentNever', async () => {
25+
const cacheManager = new PersistentNever<CacheData>()
26+
27+
await cacheManager.set('key', {
28+
result: 'val'
29+
})
30+
let val = await cacheManager.get('key')
31+
expect(val).toBe(null)
32+
33+
await cacheManager.delete('key')
34+
val = await cacheManager.get('key')
35+
expect(val).toBe(null)
36+
})
37+
})
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
interface CacheManager<T> {
2+
get(key: string): Promise<T | null>;
3+
set(key: string, val: T): Promise<void>;
4+
delete(key: string): Promise<void>;
5+
}
6+
7+
export interface PersistentCacheManager<T> extends CacheManager<T> {
8+
persistLocation: string
9+
}
10+
11+
export class MemoryCacheManager<T> implements CacheManager<T> {
12+
private cache: Map<string, T> = new Map()
13+
14+
async get(key: string): Promise<T | null> {
15+
return this.cache.get(key) ?? null
16+
}
17+
18+
async set(key: string, val: T): Promise<void> {
19+
this.cache.set(key, val)
20+
}
21+
22+
async delete(key: string): Promise<void> {
23+
this.cache.delete(key)
24+
}
25+
}
26+
27+
export class PersistentNever<T = any> implements PersistentCacheManager<T> {
28+
persistLocation: string
29+
30+
constructor() {
31+
this.persistLocation = ''
32+
}
33+
34+
async get(key: string): Promise<T | null> {
35+
return null
36+
}
37+
38+
async set(key: string, val: T): Promise<void> {
39+
// do nothing
40+
}
41+
42+
async delete(key: string): Promise<void> {
43+
// do nothing
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { MemoryCacheManager, PersistentCacheManager } from './index'
2+
3+
export class MockCacheManager<T> extends MemoryCacheManager<T> implements PersistentCacheManager<T> {
4+
persistLocation = ''
5+
6+
get = jest.fn<Promise<T | null>, [string]>(() => Promise.resolve(null))
7+
set = jest.fn<Promise<void>, [string, T]>(() => Promise.resolve())
8+
delete = jest.fn<Promise<void>, [string]>(() => Promise.resolve())
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
FixedBackoff,
3+
ExponentialBackoff,
4+
RandomizedBackoff,
5+
LimitedBackoff
6+
} from './backoff'
7+
8+
describe('retry backoff test', () => {
9+
test('test FixedBackoff', () => {
10+
const backoff = new FixedBackoff(1000)
11+
expect(backoff.getDelay()).toBe(1000)
12+
})
13+
14+
test('test ExponentialBackoff', () => {
15+
const backoff = new ExponentialBackoff(1000)
16+
expect(backoff.getDelay()).toBe(1000)
17+
expect(backoff.getDelay()).toBe(2000)
18+
expect(backoff.getDelay()).toBe(4000)
19+
expect(backoff.getDelay()).toBe(8000)
20+
})
21+
22+
test('test RandomizedBackoff', () => {
23+
const backoff = new RandomizedBackoff(new FixedBackoff(1000), 100)
24+
for (let i = 0; i < 100; i += 1) {
25+
expect(backoff.getDelay()).toBeGreaterThan(900)
26+
expect(backoff.getDelay()).toBeLessThanOrEqual(1100)
27+
}
28+
29+
const backoff2 = new RandomizedBackoff(new FixedBackoff(1000), 10000)
30+
for (let i = 0; i < 100; i += 1) {
31+
expect(backoff.getDelay()).toBeGreaterThan(0)
32+
expect(backoff.getDelay()).toBeLessThanOrEqual(11000)
33+
}
34+
})
35+
36+
test('test LimitedBackoff', () => {
37+
const backoff = new LimitedBackoff(new ExponentialBackoff(1000), 2000)
38+
expect(backoff.getDelay()).toBe(1000)
39+
expect(backoff.getDelay()).toBe(2000)
40+
expect(backoff.getDelay()).toBe(2000)
41+
expect(backoff.getDelay()).toBe(2000)
42+
})
43+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
export abstract class Backoff {
2+
abstract getDelay(): number
3+
4+
async wait() {
5+
const n = this.getDelay()
6+
if (n <= 0) {
7+
return
8+
}
9+
await new Promise(resolve => setTimeout(resolve, n))
10+
}
11+
}
12+
13+
export class FixedBackoff extends Backoff {
14+
private delay: number
15+
constructor(delay: number) {
16+
super()
17+
this.delay = delay
18+
}
19+
20+
getDelay(): number {
21+
return this.delay
22+
}
23+
}
24+
25+
export class ExponentialBackoff extends Backoff {
26+
private base: number
27+
private factor: number
28+
private next: number
29+
30+
constructor(base: number, factor = 2) {
31+
super()
32+
this.base = base
33+
this.factor = factor
34+
this.next = base
35+
}
36+
37+
getDelay(): number {
38+
const delay = this.next
39+
this.next *= this.factor
40+
return delay
41+
}
42+
}
43+
44+
const Second = 1000
45+
46+
// make the backoff delay duration plus the delta,
47+
// delta is belong to (-delta, delta), not inclusive
48+
export class RandomizedBackoff extends Backoff {
49+
private backoff: Backoff
50+
private delta: number // int, in milliseconds
51+
52+
constructor(backoff: Backoff, delta = 2 * Second) {
53+
super()
54+
this.backoff = backoff
55+
this.delta = Math.floor(delta)
56+
}
57+
58+
getDelay(): number {
59+
let diff = Math.floor(Math.random() * this.delta)
60+
diff = Math.floor(Math.random() * 2) ? diff : -diff
61+
const delay = this.backoff.getDelay() + diff
62+
return Math.max(0, delay)
63+
}
64+
}
65+
66+
export class LimitedBackoff extends Backoff {
67+
private backoff: Backoff
68+
private min: number
69+
private max: number
70+
71+
constructor(backoff: Backoff, max: number, min = 0) {
72+
super()
73+
if (min > max) {
74+
throw new Error('min should be less than or equal to max')
75+
}
76+
this.backoff = backoff
77+
this.max = max
78+
this.min = min
79+
}
80+
81+
getDelay(): number {
82+
let delay = Math.min(
83+
this.max,
84+
this.backoff.getDelay()
85+
)
86+
delay = Math.max(
87+
this.min,
88+
delay
89+
)
90+
return delay
91+
}
92+
}
93+
94+
export function getDefaultBackoff(): Backoff {
95+
const exponential = new ExponentialBackoff(3 * Second)
96+
const randomized = new RandomizedBackoff(exponential, Second)
97+
return new LimitedBackoff(randomized, 30 * Second)
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './types'
2+
export * from './backoff'
3+
export * from './retrier'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
describe('retry backoff test', () => {
2+
test('test retrier', () => {
3+
expect(1).toBe(1)
4+
})
5+
})

0 commit comments

Comments
 (0)