Skip to content

Commit 84fdfd1

Browse files
authored
Merge pull request #66 from team-layer/feature/65
feat: Added retry utility functions
2 parents 1e247aa + cc45149 commit 84fdfd1

4 files changed

Lines changed: 107 additions & 1 deletion

File tree

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ commonUtil.storage.set("user", { id: 1, name: "John" }); // Stores object in loc
8282
const user = commonUtil.storage.get<{ id: number; name: string }>("user"); // Retrieves typed object
8383
commonUtil.storage.remove("user"); // Removes item from localStorage
8484

85+
// Retry utilities
86+
const result = await commonUtil.retry(async () => {
87+
const response = await fetch("/api/data");
88+
if (!response.ok) throw new Error("API failed");
89+
return response.json();
90+
}, 3); // Retry up to 3 times
91+
92+
// More retry examples
93+
const userData = await commonUtil.retry(async () => {
94+
return await fetchUserData();
95+
}); // Uses default 3 retries
96+
97+
const fileUpload = await commonUtil.retry(async () => {
98+
return await uploadFile(file);
99+
}, 5); // Custom retry count
100+
85101
// Search Query utilities
86102
const queryParams = searchQueryUtil.getAllQuery(); // { key: ["value1", "value2"], id: "123" }
87103

@@ -193,6 +209,9 @@ storage.set("data", { key: "value" });
193209
- 🛡️ **Error Handling**: Comprehensive error handling with automatic cleanup of corrupted data
194210
- 🔄 **Auto Serialization**: Automatic JSON serialization/deserialization for complex data types
195211

212+
### Retry
213+
214+
- `retry<T>(fn: () => Promise<T>, loop?: number): Promise<T>` - Retries an asynchronous function up to the specified number of times (default 3) if it fails. Automatically re-attempts on error and returns the result of the first successful execution, or throws the last error if all retries fail.
196215
### SearchQueryUtil
197216

198217
- `getAllQuery(): Record<string, string | string[]>` - Parses the current URL's query string and returns an object with key-value pairs. Values appear as arrays when the same key is used multiple times.

package/commonUtil/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export { default as encodeBase64 } from "./encodeBase64";
66
export { default as decodeBase64 } from "./decodeBase64";
77
export { default as debounce } from "./debounce";
88
export { default as throttle } from "./throttle";
9-
export { default as storage } from "./storage";
9+
export { default as retry } from "./retry";
10+
export { default as storage } from "./storage";
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { describe, expect, test, vi } from "vitest";
2+
import retry from ".";
3+
4+
describe("retry 유틸 함수 테스트", () => {
5+
test("정상적으로 실행되는 함수는 한번만 실행한다.", async () => {
6+
const fn = vi.fn().mockResolvedValue("success!");
7+
8+
const result = await retry(fn, 3);
9+
10+
expect(result).toBe("success!");
11+
expect(fn).toHaveBeenCalledTimes(1);
12+
});
13+
14+
test("함수를 실행하며 오류가 발생하면 재시도한다.", async () => {
15+
const fn = vi
16+
.fn()
17+
.mockRejectedValueOnce(new Error("fail 1"))
18+
.mockRejectedValueOnce(new Error("fail 2"))
19+
.mockResolvedValue("success!");
20+
21+
const result = await retry(fn, 3);
22+
23+
expect(result).toBe("success!");
24+
expect(fn).toHaveBeenCalledTimes(3);
25+
});
26+
27+
test("재시도 한도를 초과하면 마지막 에러를 던진다.", async () => {
28+
const error = new Error("always fail");
29+
const fn = vi.fn().mockRejectedValue(error);
30+
31+
await expect(retry(fn, 2)).rejects.toThrow("always fail");
32+
expect(fn).toHaveBeenCalledTimes(2);
33+
});
34+
35+
test("기본 재시도 횟수는 3번이다.", async () => {
36+
const fn = vi.fn().mockRejectedValue(new Error("fail"));
37+
38+
await expect(retry(fn)).rejects.toThrow("fail");
39+
expect(fn).toHaveBeenCalledTimes(3);
40+
});
41+
42+
test("타입 안전성: 반환 타입이 올바르게 추론된다.", async () => {
43+
const stringFn = vi.fn().mockResolvedValue("hello");
44+
const numberFn = vi.fn().mockResolvedValue(42);
45+
const objectFn = vi.fn().mockResolvedValue({ id: 1 });
46+
47+
const stringResult = await retry(stringFn);
48+
const numberResult = await retry(numberFn);
49+
const objectResult = await retry(objectFn);
50+
51+
expect(typeof stringResult).toBe("string");
52+
expect(typeof numberResult).toBe("number");
53+
expect(typeof objectResult).toBe("object");
54+
55+
expect(stringResult).toBe("hello");
56+
expect(numberResult).toBe(42);
57+
expect(objectResult).toEqual({ id: 1 });
58+
});
59+
60+
test("0번 재시도 시 한 번만 실행한다.", async () => {
61+
const fn = vi.fn().mockRejectedValue(new Error("fail"));
62+
63+
await expect(retry(fn, 0)).rejects.toThrow("fail");
64+
expect(fn).toHaveBeenCalledTimes(1);
65+
});
66+
});

package/commonUtil/retry/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export default async function retry<T>(
2+
fn: () => Promise<T>,
3+
loop: number = 3
4+
): Promise<T> {
5+
let count = 0;
6+
7+
const attempt = async (): Promise<T> => {
8+
try {
9+
return await fn();
10+
} catch (e) {
11+
count++;
12+
if (count >= loop) {
13+
throw e;
14+
}
15+
return attempt();
16+
}
17+
};
18+
19+
return attempt();
20+
}

0 commit comments

Comments
 (0)