From 2c9d589f0471ccee8c8b8633d5709931599ddfa3 Mon Sep 17 00:00:00 2001 From: d5ng Date: Mon, 3 Mar 2025 20:44:25 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9C=A8=20Feat:=20Promise=20then=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 82 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 assignment/src/promise.ts diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts new file mode 100644 index 0000000..4b8b03a --- /dev/null +++ b/assignment/src/promise.ts @@ -0,0 +1,82 @@ +type Executor = (resolve: Resolve, reject: Reject) => void +type Resolve = (value: T) => void +type Reject = (reason?: any) => void + +class MyPromise { + private promiseState: "pending" | "fulfilled" | "rejected" = "pending" + private promiseResult: T | null | undefined = null + private resolveQueue: any[] = [] + private rejectQueue: any[] = [] + + constructor(executor: Executor) { + const bindingResolve = this._resolve.bind(this) + const bindingReject = this._reject.bind(this) + + executor(bindingResolve, bindingReject) + } + + private _resolve(value: any) { + if (this.promiseState !== "pending") { + return + } + + this.promiseState = "fulfilled" + this.promiseResult = value + + this.resolveQueue.forEach((fn) => fn(this.promiseResult)) + this.resolveQueue = [] + } + + private _reject(reason?: any) { + if (this.promiseState !== "pending") { + return + } + + this.promiseState = "rejected" + this.promiseResult = reason + this.rejectQueue.forEach((fn) => fn(this.promiseResult)) + this.rejectQueue = [] + } + + then(onFulfilled: Resolve, onRejected?: Reject) { + return new MyPromise((resolve, reject) => { + if (this.promiseState === "pending") { + this.resolveQueue.push((value: any) => { + queueMicrotask(() => { + try { + const fulfilledValue = onFulfilled(value) + resolve(fulfilledValue) + } catch (error) { + reject(error) + } + }) + }) + + this.rejectQueue.push((reason: any) => { + queueMicrotask(() => { + if (onRejected) { + try { + const rejectedValue = onRejected(reason) + resolve(rejectedValue) + } catch (error) { + reject(error) + } + } else { + reject(reason) + } + }) + }) + } + }) + } +} + +const p = new MyPromise((resolve, reject) => { + setTimeout(() => { + resolve(1) + }, 300) +}) + +console.log("start") +p.then((value) => value + 1).then(console.log) +console.log("end") From 6dba04155395e3cc932fad42e47dee6e298e20ba Mon Sep 17 00:00:00 2001 From: d5ng Date: Mon, 3 Mar 2025 20:59:52 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20Feat:=20Promise=20catch=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 47 +++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts index 4b8b03a..0942f77 100644 --- a/assignment/src/promise.ts +++ b/assignment/src/promise.ts @@ -1,5 +1,5 @@ type Executor = (resolve: Resolve, reject: Reject) => void -type Resolve = (value: T) => void +type Resolve = (value: T) => void | T type Reject = (reason?: any) => void class MyPromise { @@ -15,7 +15,7 @@ class MyPromise { executor(bindingResolve, bindingReject) } - private _resolve(value: any) { + private _resolve(value: T) { if (this.promiseState !== "pending") { return } @@ -38,16 +38,24 @@ class MyPromise { this.rejectQueue = [] } - then(onFulfilled: Resolve, onRejected?: Reject) { - return new MyPromise((resolve, reject) => { + catch(onRejected: Reject) { + return this.then(undefined, onRejected) + } + + then(onFulfilled: Resolve | undefined, onRejected?: Reject) { + return new MyPromise((resolve, reject) => { if (this.promiseState === "pending") { - this.resolveQueue.push((value: any) => { + this.resolveQueue.push((value: T) => { queueMicrotask(() => { - try { - const fulfilledValue = onFulfilled(value) - resolve(fulfilledValue) - } catch (error) { - reject(error) + if (onFulfilled) { + try { + const fulfilledValue = onFulfilled(value) + resolve(fulfilledValue as T) + } catch (error) { + reject(error) + } + } else { + resolve(value) } }) }) @@ -57,7 +65,7 @@ class MyPromise { if (onRejected) { try { const rejectedValue = onRejected(reason) - resolve(rejectedValue) + resolve(rejectedValue as T) } catch (error) { reject(error) } @@ -77,6 +85,21 @@ const p = new MyPromise((resolve, reject) => { }, 300) }) +// const p = new Promise((resolve, reject) => { +// setTimeout(() => { +// resolve(1) +// }, 300) +// }) + console.log("start") -p.then((value) => value + 1).then(console.log) +p.then((value) => value + 1) + .then((value) => { + if (value < 10) { + throw new Error("value가 10보다 작아요") + } + + return value + }) + .catch(console.log) + console.log("end") From feb437dbdae3fc494692d0a07cf539c5dd505113 Mon Sep 17 00:00:00 2001 From: d5ng Date: Mon, 3 Mar 2025 21:08:50 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9C=A8=20Feat:=20then=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 46 +++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts index 0942f77..55ba83f 100644 --- a/assignment/src/promise.ts +++ b/assignment/src/promise.ts @@ -44,6 +44,40 @@ class MyPromise { then(onFulfilled: Resolve | undefined, onRejected?: Reject) { return new MyPromise((resolve, reject) => { + if (this.promiseState === "fulfilled") { + this.resolveQueue.push((value: T) => { + queueMicrotask(() => { + if (onFulfilled) { + try { + const fulfilledValue = onFulfilled(value) + resolve(fulfilledValue as T) + } catch (error) { + reject(error) + } + } else { + resolve(value) + } + }) + }) + } + + if (this.promiseState === "rejected") { + this.rejectQueue.push((reason: any) => { + queueMicrotask(() => { + if (onRejected) { + try { + const rejectedValue = onRejected(reason) + resolve(rejectedValue as T) + } catch (error) { + reject(error) + } + } else { + reject(reason) + } + }) + }) + } + if (this.promiseState === "pending") { this.resolveQueue.push((value: T) => { queueMicrotask(() => { @@ -85,13 +119,8 @@ const p = new MyPromise((resolve, reject) => { }, 300) }) -// const p = new Promise((resolve, reject) => { -// setTimeout(() => { -// resolve(1) -// }, 300) -// }) - console.log("start") + p.then((value) => value + 1) .then((value) => { if (value < 10) { @@ -100,6 +129,9 @@ p.then((value) => value + 1) return value }) - .catch(console.log) + .catch((error) => { + return 100 + }) + .then((value) => console.log(value)) console.log("end") From 0317547861cec184b7e46ec697a3cc8a41c019c4 Mon Sep 17 00:00:00 2001 From: d5ng Date: Mon, 3 Mar 2025 21:18:47 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20then=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 89 ++++++++++++++------------------------- 1 file changed, 32 insertions(+), 57 deletions(-) diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts index 55ba83f..3d0541d 100644 --- a/assignment/src/promise.ts +++ b/assignment/src/promise.ts @@ -44,71 +44,46 @@ class MyPromise { then(onFulfilled: Resolve | undefined, onRejected?: Reject) { return new MyPromise((resolve, reject) => { - if (this.promiseState === "fulfilled") { - this.resolveQueue.push((value: T) => { - queueMicrotask(() => { - if (onFulfilled) { - try { - const fulfilledValue = onFulfilled(value) - resolve(fulfilledValue as T) - } catch (error) { - reject(error) - } - } else { - resolve(value) + const enqueueFulfilled = (value: T) => { + queueMicrotask(() => { + if (onFulfilled) { + try { + const fulfilledValue = onFulfilled(value) + resolve(fulfilledValue as T) + } catch (error) { + reject(error) } - }) + } else { + resolve(value) + } }) } - if (this.promiseState === "rejected") { - this.rejectQueue.push((reason: any) => { - queueMicrotask(() => { - if (onRejected) { - try { - const rejectedValue = onRejected(reason) - resolve(rejectedValue as T) - } catch (error) { - reject(error) - } - } else { - reject(reason) + const enqueueRejected = (reason: any) => { + queueMicrotask(() => { + if (onRejected) { + try { + const rejectedValue = onRejected(reason) + resolve(rejectedValue as T) + } catch (error) { + reject(error) } - }) + } else { + reject(reason) + } }) } - if (this.promiseState === "pending") { - this.resolveQueue.push((value: T) => { - queueMicrotask(() => { - if (onFulfilled) { - try { - const fulfilledValue = onFulfilled(value) - resolve(fulfilledValue as T) - } catch (error) { - reject(error) - } - } else { - resolve(value) - } - }) - }) - - this.rejectQueue.push((reason: any) => { - queueMicrotask(() => { - if (onRejected) { - try { - const rejectedValue = onRejected(reason) - resolve(rejectedValue as T) - } catch (error) { - reject(error) - } - } else { - reject(reason) - } - }) - }) + const lookupTable = { + fulfilled: () => this.resolveQueue.push(enqueueFulfilled), + rejected: () => this.rejectQueue.push(enqueueRejected), + pending: () => { + this.resolveQueue.push(enqueueFulfilled) + this.rejectQueue.push(enqueueRejected) + }, } + + lookupTable[this.promiseState]() }) } } @@ -116,7 +91,7 @@ class MyPromise { const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) - }, 300) + }, 1000) }) console.log("start") From b161a27ed8291ce6d1d10680bb6b794c2e7b5106 Mon Sep 17 00:00:00 2001 From: d5ng Date: Mon, 3 Mar 2025 21:30:05 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9C=A8=20Feat:=20static=20resolve=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts index 3d0541d..9f2677e 100644 --- a/assignment/src/promise.ts +++ b/assignment/src/promise.ts @@ -15,6 +15,10 @@ class MyPromise { executor(bindingResolve, bindingReject) } + static resolve(value: T) { + return new MyPromise((resolve) => resolve(value)) + } + private _resolve(value: T) { if (this.promiseState !== "pending") { return @@ -75,8 +79,8 @@ class MyPromise { } const lookupTable = { - fulfilled: () => this.resolveQueue.push(enqueueFulfilled), - rejected: () => this.rejectQueue.push(enqueueRejected), + fulfilled: () => enqueueFulfilled(this.promiseResult as T), + rejected: () => enqueueRejected(this.promiseResult), pending: () => { this.resolveQueue.push(enqueueFulfilled) this.rejectQueue.push(enqueueRejected) @@ -88,11 +92,13 @@ class MyPromise { } } -const p = new MyPromise((resolve, reject) => { - setTimeout(() => { - resolve(1) - }, 1000) -}) +const p = MyPromise.resolve(100) + +// const p = new MyPromise((resolve, reject) => { +// setTimeout(() => { +// resolve(1) +// }, 1000) +// }) console.log("start") From e457dfbeb0771b43bb58226b9e843bf358ee98f6 Mon Sep 17 00:00:00 2001 From: d5ng Date: Mon, 3 Mar 2025 21:31:54 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=E2=9C=A8=20Feat:=20static=20reject=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts index 9f2677e..25b0c45 100644 --- a/assignment/src/promise.ts +++ b/assignment/src/promise.ts @@ -19,6 +19,10 @@ class MyPromise { return new MyPromise((resolve) => resolve(value)) } + static reject(value: T) { + return new MyPromise((_, reject) => reject(value)) + } + private _resolve(value: T) { if (this.promiseState !== "pending") { return @@ -91,28 +95,3 @@ class MyPromise { }) } } - -const p = MyPromise.resolve(100) - -// const p = new MyPromise((resolve, reject) => { -// setTimeout(() => { -// resolve(1) -// }, 1000) -// }) - -console.log("start") - -p.then((value) => value + 1) - .then((value) => { - if (value < 10) { - throw new Error("value가 10보다 작아요") - } - - return value - }) - .catch((error) => { - return 100 - }) - .then((value) => console.log(value)) - -console.log("end") From c8fb3205ebd5047742479b5f0f23726d075edc10 Mon Sep 17 00:00:00 2001 From: d5ng Date: Fri, 14 Mar 2025 00:01:15 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=E2=9C=A8=20Feat:=20finally=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts index 25b0c45..fc51489 100644 --- a/assignment/src/promise.ts +++ b/assignment/src/promise.ts @@ -2,7 +2,7 @@ type Executor = (resolve: Resolve, reject: Reject) => void type Resolve = (value: T) => void | T type Reject = (reason?: any) => void -class MyPromise { +export default class MyPromise { private promiseState: "pending" | "fulfilled" | "rejected" = "pending" private promiseResult: T | null | undefined = null private resolveQueue: any[] = [] @@ -94,4 +94,8 @@ class MyPromise { lookupTable[this.promiseState]() }) } + + finally(onFinally: () => void) { + return this.then(onFinally, onFinally) + } } From a752ff265f42090e2b8787af0979ad4bd88a6dd9 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sun, 16 Mar 2025 10:20:39 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9C=85=20Test:=20Promise=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/index.html | 2 +- assignment/src/promise.test.ts | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 assignment/src/promise.test.ts diff --git a/assignment/index.html b/assignment/index.html index 2bb0ce8..bf06f79 100644 --- a/assignment/index.html +++ b/assignment/index.html @@ -8,6 +8,6 @@
- + diff --git a/assignment/src/promise.test.ts b/assignment/src/promise.test.ts new file mode 100644 index 0000000..036e3cc --- /dev/null +++ b/assignment/src/promise.test.ts @@ -0,0 +1,63 @@ +import MyPromise from "./promise" + +type Callback = (resolve: (value: T) => void, reject: (reason?: any) => void) => void + +function testPromise(callback: Callback) { + return new MyPromise((resolve, reject) => { + setTimeout(() => { + callback(resolve, reject) + }, 300) + }) +} + +describe("프로미스 테스트", () => { + test("then 메서드 테스트", () => { + return testPromise((resolve) => resolve(1)).then((value) => expect(value).toEqual(1)) + }) + + test("then 메서드 체이닝", () => { + return testPromise((resolve) => resolve(1)) + .then((value) => value + 1) + .then((value) => value + 1) + .then((value) => expect(value).toEqual(3)) + }) + + test("catch 메서드 테스트 베이직", () => { + return testPromise((_, reject) => reject("에러 발생")).catch((error) => { + expect(error).toBe("에러 발생") + }) + }) + + test("catch 메서드 테스트 스탠다드", () => { + return testPromise((resolve) => resolve(1)) + .then((value) => value + 1) + .then((value) => { + if (value < 5) { + throw new Error("value가 5보다 작아요") + } + + return value + }) + .catch((error) => { + expect(error).toBeInstanceOf(Error) + }) + }) + + test("finally 메서드 테스트", () => { + const finallyMockFn = vi.fn(() => "비동기 성공, 실패 여부와 상관 없이 실행") + + return testPromise((resolve) => resolve(1)) + .then((value) => value + 1) + .then((value) => { + expect(value).toEqual(2) + throw new Error("에러 발생") + }) + .catch((error) => { + expect(error).toBeInstanceOf(Error) + }) + .finally(finallyMockFn) + .then(() => { + expect(finallyMockFn).toHaveBeenCalledTimes(1) + }) + }) +}) From 9ba8d0dc4af010a31e3d6b6a4062eca64ecc30c9 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sun, 16 Mar 2025 10:25:16 +0900 Subject: [PATCH 9/9] =?UTF-8?q?=E2=9C=A8=20Feat:=20Constructor=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assignment/src/promise.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assignment/src/promise.ts b/assignment/src/promise.ts index fc51489..2cc7780 100644 --- a/assignment/src/promise.ts +++ b/assignment/src/promise.ts @@ -12,7 +12,11 @@ export default class MyPromise { const bindingResolve = this._resolve.bind(this) const bindingReject = this._reject.bind(this) - executor(bindingResolve, bindingReject) + try { + executor(bindingResolve, bindingReject) + } catch (error) { + bindingReject(error) + } } static resolve(value: T) {