Skip to content

Commit

Permalink
(wip)feat: add retry
Browse files Browse the repository at this point in the history
- todo: mini program and harmony
  • Loading branch information
lihsai0 committed Dec 30, 2024
1 parent 97d5879 commit c74bf37
Show file tree
Hide file tree
Showing 53 changed files with 2,211 additions and 308 deletions.
41 changes: 41 additions & 0 deletions packages/browser/src/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as common from '../@internal'

export class LocalStorageCacheManager<T> implements common.cache.PersistentCacheManager<T> {
persistPath: string;

constructor(persistLocation: string) {
this.persistPath = persistLocation
}

async get(key: string): Promise<T | null> {
const itemKey = this.getPersistKey(key)
const raw = localStorage.getItem(itemKey)
if (!raw) {
return null
}
return this.parse(raw)
}

async set(key: string, val: T): Promise<void> {
const itemKey = this.getPersistKey(key)
const raw = this.stringify(val)
localStorage.setItem(itemKey, raw)
}

async delete(key: string): Promise<void> {
const itemKey = this.getPersistKey(key)
localStorage.removeItem(itemKey)
}

private getPersistKey(key: string): string {
return `qnsdk:${this.persistPath}:${key}`
}

private parse(val: string): T {
return JSON.parse(val)
}

private stringify(val: T): string {
return JSON.stringify(val)
}
}
10 changes: 9 additions & 1 deletion packages/browser/src/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,17 @@ export class HttpClient implements common.HttpClient {

mockProgress?.end()

const reqId = xhr.getResponseHeader('x-reqId')
if (!reqId) {
resolve({
error: new UploadError('HijackedError', 'Response header x-reqId not found')
})
return
}

resolve({
result: {
reqId: xhr.getResponseHeader('x-reqId') || undefined,
reqId,
code: xhr.status,
data: xhr.responseText
}
Expand Down
7 changes: 4 additions & 3 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
"build": "run-p build:*",
"dev": "run-p \"build:* -- -w\"",
"postinstall": "run-p build:*",
"build:browser": "cpx \"src/**/*\" ../browser/src/@internal",
"build:wechat-miniprogram": "cpx \"src/**/*\" ../wechat-miniprogram/src/@internal",
"build:harmony": "cpx \"src/**/*\" ../harmony/library/src/main/ets/components/@internal"
"cleanup": "rm -fr ../browser/src/@internal ../wechat-miniprogram/src/@internal ../harmony/library/src/main/ets/components/@internal",
"build:browser": "cpx \"src/**/!(*.test,*.mock).ts\" ../browser/src/@internal",
"build:wechat-miniprogram": "cpx \"src/**/!(*.test,*.mock).ts\" ../wechat-miniprogram/src/@internal",
"build:harmony": "cpx \"src/**/!(*.test,*.mock).ts\" ../harmony/library/src/main/ets/components/@internal"
},
"author": "",
"license": "ISC"
Expand Down
9 changes: 6 additions & 3 deletions packages/common/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,17 +431,20 @@ export class UploadApis {
}

interface GetHostConfigParams {
// TODO: typo
assessKey: string
bucket: string
serverUrl?: string
}

interface HostConfig {
export interface HostConfig {
hosts: Array<{
region: string
ttl: number
up: {
domains: string[]
old: string[]
acc_domains?: string[]
}
io: {
domains: string[]
Expand Down Expand Up @@ -469,8 +472,8 @@ export class ConfigApis {
async getHostConfig(params: GetHostConfigParams): Promise<Result<HostConfig>> {
/** 从配置中心获取上传服务地址 */
const query = `ak=${encodeURIComponent(params.assessKey)}&bucket=${encodeURIComponent(params.bucket)}`
// TODO: 支持设置,私有云自动获取上传地址
const url = `${this.serverUrl}/v4/query?${query}`
const serverUrl = params.serverUrl || this.serverUrl
const url = `${serverUrl}/v4/query?${query}`
const response = await this.httpClient.get(url)

if (!isSuccessResult(response)) {
Expand Down
37 changes: 37 additions & 0 deletions packages/common/src/helper/cache/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MemoryCacheManager, PersistentNever } from './index'

interface CacheData {
result: string
}

describe('CacheManager', () => {
test('test MemoryCacheManager', async () => {
const memoryCacheManager = new MemoryCacheManager<CacheData>()

await memoryCacheManager.set('key', {
result: 'val'
})
let val = await memoryCacheManager.get('key')
expect(val).toEqual({
result: 'val'
})

await memoryCacheManager.delete('key')
val = await memoryCacheManager.get('key')
expect(val).toBe(null)
})

test('test PersistentNever', async () => {
const cacheManager = new PersistentNever<CacheData>()

await cacheManager.set('key', {
result: 'val'
})
let val = await cacheManager.get('key')
expect(val).toBe(null)

await cacheManager.delete('key')
val = await cacheManager.get('key')
expect(val).toBe(null)
})
})
45 changes: 45 additions & 0 deletions packages/common/src/helper/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
interface CacheManager<T> {
get(key: string): Promise<T | null>;
set(key: string, val: T): Promise<void>;
delete(key: string): Promise<void>;
}

export interface PersistentCacheManager<T> extends CacheManager<T> {
persistLocation: string
}

export class MemoryCacheManager<T> implements CacheManager<T> {
private cache: Map<string, T> = new Map()

async get(key: string): Promise<T | null> {
return this.cache.get(key) ?? null
}

async set(key: string, val: T): Promise<void> {
this.cache.set(key, val)
}

async delete(key: string): Promise<void> {
this.cache.delete(key)
}
}

export class PersistentNever<T = any> implements PersistentCacheManager<T> {
persistLocation: string

constructor() {
this.persistLocation = ''
}

async get(key: string): Promise<T | null> {
return null
}

async set(key: string, val: T): Promise<void> {
// do nothing
}

async delete(key: string): Promise<void> {
// do nothing
}
}
9 changes: 9 additions & 0 deletions packages/common/src/helper/cache/persistent.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MemoryCacheManager, PersistentCacheManager } from './index'

export class MockCacheManager<T> extends MemoryCacheManager<T> implements PersistentCacheManager<T> {
persistLocation = ''

get = jest.fn<Promise<T | null>, [string]>(() => Promise.resolve(null))
set = jest.fn<Promise<void>, [string, T]>(() => Promise.resolve())
delete = jest.fn<Promise<void>, [string]>(() => Promise.resolve())
}
43 changes: 43 additions & 0 deletions packages/common/src/helper/retry/backoff.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
FixedBackoff,
ExponentialBackoff,
RandomizedBackoff,
LimitedBackoff
} from './backoff'

describe('retry backoff test', () => {
test('test FixedBackoff', () => {
const backoff = new FixedBackoff(1000)
expect(backoff.getDelay()).toBe(1000)
})

test('test ExponentialBackoff', () => {
const backoff = new ExponentialBackoff(1000)
expect(backoff.getDelay()).toBe(1000)
expect(backoff.getDelay()).toBe(2000)
expect(backoff.getDelay()).toBe(4000)
expect(backoff.getDelay()).toBe(8000)
})

test('test RandomizedBackoff', () => {
const backoff = new RandomizedBackoff(new FixedBackoff(1000), 100)
for (let i = 0; i < 100; i += 1) {
expect(backoff.getDelay()).toBeGreaterThan(900)
expect(backoff.getDelay()).toBeLessThanOrEqual(1100)
}

const backoff2 = new RandomizedBackoff(new FixedBackoff(1000), 10000)
for (let i = 0; i < 100; i += 1) {
expect(backoff.getDelay()).toBeGreaterThan(0)
expect(backoff.getDelay()).toBeLessThanOrEqual(11000)
}
})

test('test LimitedBackoff', () => {
const backoff = new LimitedBackoff(new ExponentialBackoff(1000), 2000)
expect(backoff.getDelay()).toBe(1000)
expect(backoff.getDelay()).toBe(2000)
expect(backoff.getDelay()).toBe(2000)
expect(backoff.getDelay()).toBe(2000)
})
})
98 changes: 98 additions & 0 deletions packages/common/src/helper/retry/backoff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
export abstract class Backoff {
abstract getDelay(): number

async wait() {
const n = this.getDelay()
if (n <= 0) {
return
}
await new Promise(resolve => setTimeout(resolve, n))
}
}

export class FixedBackoff extends Backoff {
private delay: number
constructor(delay: number) {
super()
this.delay = delay
}

getDelay(): number {
return this.delay
}
}

export class ExponentialBackoff extends Backoff {
private base: number
private factor: number
private next: number

constructor(base: number, factor = 2) {
super()
this.base = base
this.factor = factor
this.next = base
}

getDelay(): number {
const delay = this.next
this.next *= this.factor
return delay
}
}

const Second = 1000

// make the backoff delay duration plus the delta,
// delta is belong to (-delta, delta), not inclusive
export class RandomizedBackoff extends Backoff {
private backoff: Backoff
private delta: number // int, in milliseconds

constructor(backoff: Backoff, delta = 2 * Second) {
super()
this.backoff = backoff
this.delta = Math.floor(delta)
}

getDelay(): number {
let diff = Math.floor(Math.random() * this.delta)
diff = Math.floor(Math.random() * 2) ? diff : -diff
const delay = this.backoff.getDelay() + diff
return Math.max(0, delay)
}
}

export class LimitedBackoff extends Backoff {
private backoff: Backoff
private min: number
private max: number

constructor(backoff: Backoff, max: number, min = 0) {
super()
if (min > max) {
throw new Error('min should be less than or equal to max')
}
this.backoff = backoff
this.max = max
this.min = min
}

getDelay(): number {
let delay = Math.min(
this.max,
this.backoff.getDelay()
)
delay = Math.max(
this.min,
delay
)
return delay
}
}

export function getDefaultBackoff(): Backoff {
const exponential = new ExponentialBackoff(3 * Second)
const randomized = new RandomizedBackoff(exponential, Second)
return new LimitedBackoff(randomized, 30 * Second)
}
3 changes: 3 additions & 0 deletions packages/common/src/helper/retry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './types'
export * from './backoff'
export * from './retrier'
5 changes: 5 additions & 0 deletions packages/common/src/helper/retry/retrier.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('retry backoff test', () => {
test('test retrier', () => {
expect(1).toBe(1)
})
})
Loading

0 comments on commit c74bf37

Please sign in to comment.