Skip to content

dohye1(3주차):3.코드:객체:함수, (4주차):비동기 프로그래밍 #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 2, 2025
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
207 changes: 207 additions & 0 deletions dohye1/3.코드:객체:함수.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# 개념
- 지연평가
- 안전한 합성
- 에러처리
- 리스트 프로세싱

# 3.0
- 이터레이터는 컬렉션 형태의 데이터를 일반화된 패턴으로 순회하는 객체. 필요한 순간에만 값을 평가하는 지연성을 가짐.

### 이터레이터는 명령형, 객체지향적, 함수형으로 다룰수있음
명령형 : next()메서드를 사용해 while문으로 순회하거나, 전개연산자 등으로 다룰수있음
객체지향형 : 이터레이터를 다루는 클래스를 만들거나, 이터레이터 내부에서 다른 이터레이터와 통신하는 이터레이터를 만들 수 있음
함수형 : 고차함수를 통해 이터레이터의 각 요소를 처리할 함수를 전달하는 방식. 지연평가와 **리스트 프로세싱**을 극대화


# 3.1 코드가 곧 데이터 > 명령형 코드를 리스트 프로세싱으로 대체하는 방법에 대해 학습

- 명령형 코드를 조금씩 함수형방식으로 변경해나가는 과정이 흥미로웠음
- break는 필요한 만큼만 코드가 반복되도록 제어 > 로직의 효율성 > take로 변경해서 제어문도 리스트로 사고할수있는 과정이 흥미로움 > 이게 가능한이유는 지연평가 때문임!
- 결과적으로 코드가 **선언적**으로 변했고 가독성이 개선됨!
- 코드의 각 부분을 독립적인 리스트로 처리하면서 **각 로직의 역할이 명확해진다**

# 3.2 하스켈로부터 배우기
- 순수 함수형 프로그래밍 언어
- 커링, 지연평가 지원, 부수효과를 특별하게 관리, 강력한 타입 시스템
- 옵셔널 값 처리


## 커링
- 여러 인자를 받는 함수를 인자 하나씩 받는 함수들의 연쇄로 표현하는 기법

```haskell
add :: Int -> Int -> Int
add x y = x + y
```

## IO타입
> 모든 함수가 같은 인자에 대해 항상 동일한 결과를 내놓아야하는 순수성을 지향한다.
- 부수 효과가 있는 함수는 IO타입을 통해 격리한는 식으로 해결한다.
- 순수함수와 IO함수를 타입차원에서 명시하고, 구분할 수 있다. 이로인해 부수효과로 인한 예측 불가능성을 최소화할수있음!
- 부수 효과는 IO안에서만 허용된다라는 합의를 통해 순수성을 지킨다.

## Either 를 통한 에러처리
- 하스켈은 타입을 통해 에러 상황을 명시적으로 표현하는 방식을 선호한다.
- 성공(Right)과 실패(Left)를 구분하여 함수의 결과를 명확히 표현함으로써 컴파일 타임에 에러 처리가 필요함을 인지시킨다.

```haskell

main :: IO ()
main = do
print (div 10 2) -- 출력: 5
print (div 10 0) -- 예외 발생: divide by zero

safeDiv :: Int -> Int -> Either String Int
safeDiv _ 0 = Left "0으로 나눌 수 없습니다."
safeDiv x y = Right (div x y)
```
런타임 에러 발생대신 명시적으로 에러 상황을 표현할 수 있다.

## 패턴매칭
- 인자 패턴에 따라 함수 실행을 분기처리한다. (✅타입과 코드실행이 분리되어있지않다는게 신기함)
- 간결하고 직관적인 코드를 작성하도록 돕는다. **타입스크립트(함수오버로드, if문, 타입가드, 타입 좁히기, 매개변수 구조 분해 등)의 역할을 모두 패턴매칭 한번으로 해결할 수 있다.**
- 에러를 런타임 예외 대신 타입을 통해 안전하게 처리할 수 있다.

# ⭐️⭐️⭐️ 3.3 지연 평가 자세히 살펴보기
- 지연 평가를 지원하는 자료구조인 이터레이터!의 실제 실행순서를 살펴봄
- (✅어려웠지만, 이터레이터가 실행되는 순서가 파악되었고, 지연평가가 실제로 실행되는 원리를 배울수있어서 좋았음)

## 중첩된 이터레이터의 실행 순서
```typescript
function* filter<A>(f: (a: A) => boolean, iterable: Iterable<A>): IterableIterator<A> {
const iterator = iterable[Symbol.iterator]();
while (true) {
console.log('filter');
const { value, done } = iterator.next();
if (done) break;
if (f(value)) yield value;
}
}

function* map<A, B>(f: (a: A) => B, iterable: Iterable<A>): IterableIterator<B> {
const iterator = iterable[Symbol.iterator]();
while (true) {
console.log('map');
const { value, done } = iterator.next();
if (done) break;
yield f(value);
}
}

function* take<A>(limit: number, iterable: Iterable<A>): IterableIterator<A> {
const iterator = iterable[Symbol.iterator]();
while (true) {
console.log('take limit:', limit);
const { value, done } = iterator.next();
if (done) break;
yield value;
if (--limit === 0) break;
}
}

const iterable = fx([1, 2, 3, 4, 5])
.filter(a => a % 2 === 1)
.map(a => a * a)
.take(2);

for (const a of iterable) {
console.log('result:', a);
}
// ?
// ?

// take limit: 2
// map
// filter
// result: 1
// take limit: 1
// map
// filter
// filter
// result: 9
```
- take 함수까지 실행한 결과로 만들어진 이터레이터
- iterator.next();를 호출하기때문에 점점 상위의 호출로 이어지게됨
- 함수 호출이아닌 대기상태이다!
```typescript
const iterable = fx([1, 2, 3, 4, 5])
.filter(a => a % 2 === 1)
.map(a => a * a)
.take(2);
```

### 원리
```typescript
const filtered = {
next() {
return iterator.next();
}
}

const mapped = {
next() {
return filtered.next();
}
}

const taked = {
next() {
return mapped.next();
}
};

taked.next();
```

# 3.4 Generator:Iterator:LISP 지연 평가와 안전한 합성
- 고차함수를 리스트 프로세싱 함수의 조합만으로 구현

### find 함수 시그니처
```typescript
type Find = <A>(f: (a: A) => boolean, iterable: Iterable<A>) => A | undefined;
```

```haskell
find :: (a -> Bool) -> [a] -> Maybe a
```
Maybe a는 찾는 조건을 만족하는 첫 번째 요소가 있을 경우 Just a를, 없을 경우 Nothing을 반환하는 타입


### 궁극적으로, 이터레이터는 다음 세 가지 방식으로 만들 수 있으며, 이들은 서로를 1:1:1로 대체 가능
> 명령형 방식(IP) - 제너레이터를 통한 이터레이터 생성
> <br />
> 객체 지향적 방식(OOP) - 이터레이터 객체 직접 구현
> <br />
> 함수형 방식(FP) - 리스트 프로세싱 함수 조합으로 이터레이터 생성


## 지연 평가에 기반해 break 로직 끼워 넣기
### take 함수
```typescript
function* take<A>(limit: number, iterable: Iterable<A>): IterableIterator<A> {
const iterator = iterable[Symbol.iterator]();
while (true) {
console.log('take limit:', limit);
const { value, done } = iterator.next();
if (done) break;
yield value;
if (--limit === 0) break;
}
}
```
- some 함수는 모든 인자값들을 한번씩 순회할 필요없이 지연평가에 의해 특정 조건이 충족되면 순회를 종료 : 효율

```typescript
function some<A>(f: (a: A) => boolean, iterable: Iterable<A>): boolean {
return fx(iterable)
.map(f)
.filter(a => a)
.take(1)
.reduce((a, b) => a || b, false); // [a: boolean], [b: boolean]
}

console.log(some(isOdd, [2, 5, 6]));
// true
console.log(some(isOdd, [2, 4, 6]));
// false
```
186 changes: 186 additions & 0 deletions dohye1/4.비동기 프로그래밍.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# 4.0
- 비동기 프로그래밍은 특정 작업이 수행될때까지 기다리지 않고, 다른 작업을 계속 수행하는 프로그래밍 방식
- 자바스크립트가 실행되는 환경에서는 대부분 싱글 스레드 기반의 비동기 IO를 통해 프로그램의 동시실행을 지원
- Node.js는 싱글 스레드로 동작하지만 비동기 IO를 통해 외부 자원에게 병렬 작업을 맡기고 실행순서를 제어

# 4.1 값으로 다루는 비동기
## Promise
- 비동기 작업의 결과를 값으로 다룰수있게하는 객체이자 규약
- 비동기 상황을 타입수준에서 다룰 수 있게 한다. 컴파일 타임에 안전한 합성이 가능해짐!
- **비동기 작업의 완료 여부와 관계없이 즉시 객체를 생성하여 값으로 다룰수있게 하고, 비동기 작업의 결과가 필요한 시점에 값/에러를 처리할수있도록 한다**
- 2017년부터 async/await가 도입되어 Promise를 더욱 간결하고 직관적으로 사용할수있게 되었다.


- 실무에서 비동기 병렬 실행 제어 함수를 구현해야할때 Promise.all, Promise.race 같은 메서드를 사용해야하는 경우도있다.
- `new Promise()`를 많이 다뤄봤다는것은 주어진 환경을 소비하는 수준을 넘어 비동기 제어에 관한 깊이있는 이해와 문제해결 능력을 갖추고 있는지를 평가하는 척도가 될 수 있다.

### Promise.race
- 병렬로 실행된 여러 Promise 중 가장 먼저 완료된 Promise의 결과나 에러를 반환한다.
- 특정 API 요청이 일정시간내로 응답이 돌아오지않을떄의 방어처리를 할때 사용할 수 있음

```typescript
const result = new Promise.race({
getFriends(),
delay(5000, 'timeout')
})
```

이런 경우엔 `AbortController`도 사용 가능

```typescript
let controller;
async function fetchVideo() {
controller = new AbortController();
const signal = controller.signal;

try {
const response = await fetch(url, { signal });
console.log("Download complete", response);
// process response further
} catch (err) {
console.error(`Download error: ${err.message}`);
}
}

controller.abort(); // 호출 시 fetch 취소됨
```

### Promise.all
- 주어진 모든 Promise가 이행될때까지 기다렸다가 모든 결과를 배열로 반환하는 함수이다.
- 주어진 Promise 중 하나라도 reject 되면 Promise.all은 즉시 거부되고 거부 이유를 반환한다. error가 throw됨
- 병령작업이 모두 실행하고 완료될때까지 기다릴떄 유용

### Promise.allSettled
- 주어진 모든 Promise가 완료될때까지 기다렸다가 각 Promise의 성공/실패 결과를 객체로 담아 반환한다.
- 모든 Promise의 완료상태를 확인하고 싶을때 유용
- 아래 형태로 결과값이 반환된다.

```typescript
[
{ status: 'fulfilled', value: {} },
{ status: 'fulfilled', value: {} },
{ status: 'fulfilled', value: {} },
{ status: 'rejected', reason:'동작 실패' }
]
```
> 에러가 전파되는것을 원할때는 Promise.all을 사용해야한다.

### Promise.any
- `Promise.race`는 가장 먼저 완료된 Promise를 **이행되든 거부되든 상관없이** 즉시 그 결과를 반환한다.
- `Promise.any`는 여러 Promise 중 가장 먼저 이행된 Promise의 값을 반환한다.
- 단 모든 Promise가 거부된경우엔 모든 이유를 포함하는 단일 에러를 반환한다.


# 4.2 지연성으로 다루는 비동기
- Promise는 생성되는 즉시 실행된다.

### 병렬 실행의 의미
- 🚨 Promise.all은 이미 실행된 모든 Promise를 받아 모두 완료될때까지 대기했다가 각 Promise를 풀어낸 배열을 반환하는 함수일뿐이다.
- 🚨 Promise의 시작 자체를 제어하는 함수는 아니다!!

- Promise.all에 Promise를 넘겨주는것이아니라, Promise를 실행할수있는 함수를 넘겨주면 Promise의 실행을 지연할수있음

## 지연성
- 리스트 프로세싱에서 지연성을 활용해서 효과적으로 비동기 작업을 처리
- 코드의 가독성/유지보수성이 뛰어나다!

```typescript
const executeWithLimit = <T>(fs: (() => Promise<T>)[], limit: number): Promise<T[]> =>
fx(fs)
.chunk(limit)
.map(fs=>fs.map(f=>f()))
.map(ps=>Promise.all(ps))
.to(fromAsync)
.then(arr=>arr.flat())
```
- executeWithLimit함수는 실행이 지연된 Promise 함수를 인자로 받는다.
- fromAsync에서 하나의 요소를 꺼낼때 해당 청크안에 있는 함수들만 실행하고 그 다음번 map에서 Promise.all로 감싼다.
- fromAsync 내부의 for await of 구문에서 이터레이터가 처음 소비될때 3개의 f가 실행되고 그룹화되어 Promise.all에 전달된다.


# 4.3 타입으로 다루는 비동기
- 자바스크립트는 AsyncIterator, AsyncIterable, AsyncGenerator와 같은 프로토콜을 제공하여 비동기 작업의 순차적 처리를 지원한다.

<details>
<summary>인터페이스</summary>
<div markdown="1">

```typescript
interface IteratorYieldResult<T> {
done?: false;
value: T;
}

interface IteratorReturnResult {
done: true;
value: undefined;
}

// for...of구문으로 순회
interface Iterator<T> {
next(): IteratorYieldResult<T> | IteratorReturnResult;
}

// Promise를 반환하는 next 메서드를 가진 인터페이스
// form await...of구문으로 순회
interface AsyncIterator<T> {
next(): Promise<IteratorYieldResult<T> | IteratorReturnResult>;
}

interface AsyncIterable<T> {
[Symbol.asyncIterator](): AsyncIterator<T>
}
```
</div>
</details>

## AsyncGenerator
- 비동기적으로 값을 생성하고 순차적으로 처리하는 기능
```typescript
async function* stringsAsyncTest () {
yield delay(100, 'a');

const b = await delay(500, 'b') + 'c';
yield b
}
```
## toAsync
- 동기와 비동기를 동시에 지원하는 함수로 만드는 규약
- 동기적인 Iterable 또는 Promise가 포함된 Iterable을 받아 비동기적으로 처리할수있는 AsyncIterable로 변환한다.
```typescript
async function* toAsync<T>(
iterable: Iterable<T|Promise<T>>
):AsyncIterableIterator<Awaited<T>> {
for await (const value of iterable){
yield value
}
}

// 사용
for await (const a of toAsync([1, 2])){
console.log(a)
}
```

# 4.4 비동기 에러핸들링
- 비동기에서 에러를 효과적으로 처리하는것은 필수!
- 에러처리가 적절하지않으면 성능&부수효과&디버깅 어려움

## 에러가 제대로 발생하도록 하는것이 핵심
- 비동기 프로그래밍에서 가장 중요한것은 에러를 핸들링하는것이아니라 에러가 제대로 발생되도록 설계하는것
- 에러가 발생해야 할 상황에서 이를 적절히 발생시키는것은 코드의 신뢰성과 유지보수성을 높이는 핵심 원칙

- 에러를 발생시키는 책임을 함수 내부에 두지않고 호출하는 쪽에서 처리할수있게 설계하기. 그러면 코드의 순수성유지 및 더 나은 에러핸들링이 가능해진다.
- 에러 핸들링은 에러가 발생하는 맥락에 가깝게 작성해야 가장 효과적이다.
- 호출하는 쪽에서 처리하도록 하면 각 호출자가 자신에게 필요한 방식으로 에러를 핸들링할 수 있는 유연성을 가질수있다.

### 에러를 숨기지 않고 명확히 드러내기
- 불필요하게 에러를 처리하려고하거나 복잡한 에러 핸들링 코드를 작성하면 오히려 에러가 숨겨질 가능성이 높다.
- 에러를 감추기보다 발생하도록 두고 이를 상위레벨에서 처리해라

### 순수함수는 에러를 발생시키도록 설계
- 에러 발생을 상위호출자에게 위임

### 에러 핸들링 코드는 부수 효과 코드 근처에서 작성
- 에러 원인파악 및 해결방안을 명확히 할 수 있다.
- 부수효과와 무관한 영역에서 에러를 처리하려고하면 디버깅과 유지보수가 어려워진다.