[learning javascript] chapter 14. 비동기적 프로그래밍

비동기적 프로그래밍

비유

콜백

setInterval과 clearInterval

스코프와 비동기적 실행

오류 우선 콜백

const fname = ‘may_or_may_not_exist.txt’; fs.readFile(fname, function(err, data) { if(err) return console.error(error reading file ${fname}: ${err.message}); console.log(${fname} contents: ${data}); });

- 콜백에서 가장 먼저 하는 일은 err이 참 같은 값인지 확인하는 것
- err이 참 같은 값이라면 파일을 읽는 데 문제가 있다는 뜻으로 콘솔에 오류를 보고하고 즉시 빠져나옴
- 오류 우선 콜백을 사용할 때 가장 많이 벌어지는 실수는 빠져나와야 한다는 사실을 잊는 것
- 프라미스를 사용하지 않으면 오류 우선 콜백은 노드 개발의 표준이나 다름 없음

#### 콜백 헬
- 세 가지 파일의 컨텐츠를 읽고, 60초가 지난 다음 이들을 결합해 네 번째 파일에 기록하는 로직
```javascript
const fs = require('fs');

fs.readFile('a.txt', function(err, dataA) {
    if(err) console.error(err);
    fs.readFile('b.txt', function(err, dataB) {
        if(err) console.error(err);
        fs.readFile('c.txt', function(err, dataC) {
            if(err) console.error(err);
            setTimeout(function() {
                fs.writeFile('d.txt', dataA+dataB+dataC, function(err) {
                    if(err) console.error(err);
                })
            }, 60*1000);
        });
    });
});

프라미스

프라미스 만들기

프라미스 사용

이벤트

class Countdown extends EventEmitter { constructor(seconds, superstitious) { super(); this.seconds = seconds; this.superstitious = !!superstitious; } go() { const coundown = this; return new Promise(function(resolve, reject) { for(let i=countdown.seconds; i>=0; i–) { setTimeout(function() { if(countdown.superstitious && i === 13) return reject(new Error(“Oh my god”)); countdown.emit(‘tick’, i); if(i === 0) resolve(); }, (countdown.seconds-i)*1000); } }); } }

- `EventEmitter`를 상속하는 클래스는 이벤트를 발생시킬 수 있음
- 카운트다운을 시작하고 프라미스를 반환하는 부분은 `go`메서드
- `countdown`에 `this`를 할당하여 카운트다운이 얼마나 남았는지, 13인지 아닌지 확인
- `this`는 특별한 변수이고 콜백안에서는 값이 달라짐
- 따라서 `this`의 현재 값을 다른 변수에 저장해야 프라미스 안에서 쓸 수 있음
- 여기에서 가장 중요한 부분은 `countdown.emit('tick', i)`임.
- `tick`이벤트를 발생시키고 필요하다면 프로그램의 다른부분에서 이 이벤트를 주시할 수 있음
```javascript
const c = new Countdown(5);

c.on('tick', function(i) {
    if(i>0) console.log(i + '...');
});
c.go()
    .then(function() {
        console.log('GO!');
    })
    .catch(function(err) {
        console.error(err.message);
    })

c.go() .then(function() { console.log(‘GO!’); }) .catch(function(err) { console.error(err.message); })

- 더 진행할 수 없다는 사실을 아는 즉시 대기중인 타임아웃을 모두 취소
```javascript
const EventEmitter = require('events').EventEmitter;

class Countdown extends EventEmitter {
    constructor(seconds, superstitious) {
        super();
        this.seconds = seconds;
        this.superstitious = !!superstitious;
    }
    go() {
        const coundown = this;
        const timeoutIds = [];
        return new Promise(function(resolve, reject) {
            for(let i=countdown.seconds; i>=0; i--) {
                timeoutIds.push(
                    setTimeout(function() {
                    if(countdown.superstitious && i === 13) {
                        // 대기중인 타임아웃을 모두 취소
                        timeoutIds.forEach(clearTimeout);
                        return reject(new Error("Oh my god"));                        
                    }
                    countdown.emit('tick', i);
                    if(i === 0) resolve();
                }, (countdown.seconds-i)*1000));
            }
        });
    }
}

프라미스 체인

c.go() .then(launch) .then(function(msg) { console.log(msg); }) .catch(function(err) { console.error(“Houston, we have a problem….”); })

- 프라미스 체인을 사용하면 모든 단계에서 에러를 캐치할 필요 ㅇ벗음
- 체인 어디에서든 에러가 생기면 체인 전체가 멈추고 `catch`핸들러가 동작

#### 결정되지 않는 프라미스 방지하기
- 프라미스는 비동기적 코드를 단순화하고 콜백이 두 번 이상 실행되는 문제를 방지
- `resolve`나 `reject`를 호출하는 걸 잊어 프라미스가 결정되지 않는 문제까지 자동으로 해결하지는 못함
- 에러가 일어나지 않아 이런 실수는 찾기 매우 어려움
- 결정되지 않은 프라미스를 방지하는 한 가지 방법은 타임아웃을 거는 것
- `launch`함수에 에러 조건을 넣고, 발사하는 로켓은 열 번에 다섯 번은 실패하는 로켓으로 작성
```javascript
function launch() {
    return new Promise(function(resolve, reject) {
        if(Math.random() < 0.5) return;
        console.log("Lift off!");
        setTimeout(function() {
            resolve("In orbit!");
        }, 2*1000);
    });
}

제너레이터

1보 전진과 2보 후퇴?

제너레이터 실행기를 직접 만들지 마세요

제너레이터 실행기와 예외 처리

요약