Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ commonUtil.storage.set("user", { id: 1, name: "John" }); // Stores object in loc
const user = commonUtil.storage.get<{ id: number; name: string }>("user"); // Retrieves typed object
commonUtil.storage.remove("user"); // Removes item from localStorage

// Retry utilities
const result = await commonUtil.retry(async () => {
const response = await fetch("/api/data");
if (!response.ok) throw new Error("API failed");
return response.json();
}, 3); // Retry up to 3 times

// More retry examples
const userData = await commonUtil.retry(async () => {
return await fetchUserData();
}); // Uses default 3 retries

const fileUpload = await commonUtil.retry(async () => {
return await uploadFile(file);
}, 5); // Custom retry count

// Search Query utilities
const queryParams = searchQueryUtil.getAllQuery(); // { key: ["value1", "value2"], id: "123" }

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

### Retry

- `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.
### SearchQueryUtil

- `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.
Expand Down
3 changes: 2 additions & 1 deletion package/commonUtil/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export { default as encodeBase64 } from "./encodeBase64";
export { default as decodeBase64 } from "./decodeBase64";
export { default as debounce } from "./debounce";
export { default as throttle } from "./throttle";
export { default as storage } from "./storage";
export { default as retry } from "./retry";
export { default as storage } from "./storage";
66 changes: 66 additions & 0 deletions package/commonUtil/retry/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, expect, test, vi } from "vitest";
import retry from ".";

describe("retry 유틸 함수 테스트", () => {
test("정상적으로 실행되는 함수는 한번만 실행한다.", async () => {
const fn = vi.fn().mockResolvedValue("success!");

const result = await retry(fn, 3);

expect(result).toBe("success!");
expect(fn).toHaveBeenCalledTimes(1);
});

test("함수를 실행하며 오류가 발생하면 재시도한다.", async () => {
const fn = vi
.fn()
.mockRejectedValueOnce(new Error("fail 1"))
.mockRejectedValueOnce(new Error("fail 2"))
.mockResolvedValue("success!");

const result = await retry(fn, 3);

expect(result).toBe("success!");
expect(fn).toHaveBeenCalledTimes(3);
});

test("재시도 한도를 초과하면 마지막 에러를 던진다.", async () => {
const error = new Error("always fail");
const fn = vi.fn().mockRejectedValue(error);

await expect(retry(fn, 2)).rejects.toThrow("always fail");
expect(fn).toHaveBeenCalledTimes(2);
});

test("기본 재시도 횟수는 3번이다.", async () => {
const fn = vi.fn().mockRejectedValue(new Error("fail"));

await expect(retry(fn)).rejects.toThrow("fail");
expect(fn).toHaveBeenCalledTimes(3);
});

test("타입 안전성: 반환 타입이 올바르게 추론된다.", async () => {
const stringFn = vi.fn().mockResolvedValue("hello");
const numberFn = vi.fn().mockResolvedValue(42);
const objectFn = vi.fn().mockResolvedValue({ id: 1 });

const stringResult = await retry(stringFn);
const numberResult = await retry(numberFn);
const objectResult = await retry(objectFn);

expect(typeof stringResult).toBe("string");
expect(typeof numberResult).toBe("number");
expect(typeof objectResult).toBe("object");

expect(stringResult).toBe("hello");
expect(numberResult).toBe(42);
expect(objectResult).toEqual({ id: 1 });
});

test("0번 재시도 시 한 번만 실행한다.", async () => {
const fn = vi.fn().mockRejectedValue(new Error("fail"));

await expect(retry(fn, 0)).rejects.toThrow("fail");
expect(fn).toHaveBeenCalledTimes(1);
});
});
20 changes: 20 additions & 0 deletions package/commonUtil/retry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default async function retry<T>(
fn: () => Promise<T>,
loop: number = 3
): Promise<T> {
let count = 0;

const attempt = async (): Promise<T> => {
try {
return await fn();
} catch (e) {
count++;
if (count >= loop) {
throw e;
}
return attempt();
}
};

return attempt();
}