러닝 자바스크립트 스터디 자료 - CHAPTER 14. 비동기적 프로그래밍

사용자의 행동은 전적으로 비동기적이다. 사용자가 언제 클릭할지, 터치할지, 또는 타이핑 할지 프로그래머는 전혀 알수 없기 때문이다. 하지만 비동기적 실행이 사용자입력 하나 때문에 필요한 것은 아니다.

자바스크립트 애플리케이션은 단일 스레드에서 동작한다.
즉 자바스크립트는 한 번에 한가지 일만 할 수 있다. 이러한 자바스크립트의 본질 때문에 비동기적 프로그램밍이 필요하다.

자바스크립트에는 매우 일찍부터 비동기적 실행 메커니즘이 존재 했지만, 자바스크립트의 인기가 높아지고 소프트웨어가 복잡해짐에 따라 비동기적 프로그래밍에 필요한 장치들이 추가 되었다.

자바스크립트의 비동기적 프로그래밍에는 뚜렷이 구분되는 세가지 패러다임이 있습니다.
처음에는 콜백 그 뒤로 프라미스 마지막은 제너레이터 입니다.

제너레이터 자체는 비동기적 프로그래밍을 전혀 지원하지 않는다.
제너레이터를 비동기적으로 사용하려면 프라미스나 특수한 콜백과 함께 사용해야 한다.

사용자 입력 외에, 비동기적 테크닉을 사용해야 하는 경우는 크게 세가지가 있다.

Ajax 호출을 비롯한 네트워크 요청
파일을 읽고 쓰는 등의 파일시스템 작업
의도적으로 시간 지연을 사용하는 기능(알람 등)

비유

어떤 음식점은 당신이 줄을 서서 기다리지 않도록, 당신의 전화번호를 받아서 자리가 나면 전화를 해줍니다. 이런 음심적은 콜백과 비슷하다. 자리가 나면 당신이 알 수 있도록 하는 수단을 당신이 음식점 주인에게 넘겨준다. 음식점은 다른 손님을 대접하면 되고 당신은 다른 일을 하면된다. 어느쪽도 서로를 기다리지 않는다.

다른 음식점은 자리가 났을 때 진동하는 호출기를 당신에게 넘겨준다. 이런 음식점은 프라미스와 비슷하다.
자리가 나면 당신이 알수 있도록 하는 수단을 음식점에서 당신에게 넘겨준다.

콜백

콜백은 자바스크립트에서 가장 오래된 비동기적 메커니즘이다. 콜백은 간단히 말해 나중에 호출할 함수이다.
콜백 함수 자체에는 특별할 것이 전혀 없다. 콜백 함수도 일반적인 자바스크립트 함수일 뿐이다.
콜백함수는 일반적으로 다른 함수에 넘기거나 객체의 프로퍼티로 사용한다. 드물게는 배열에 넣어서 쓸 때도 있다.
항상 그런건 아니지만 콜백은 보통 익명 함수로 사용한다.

비동기적 실행의 가장 큰 목적이자 중요한 요점은 어떤 것도 차단하지 않는다는 것이다.
자바스크립트는 싱글 스레드를 사용하기 때문에 우리가 컴퓨터에 60초동안 대기한 후 코드를 실행하라고 지시한다면 그 실행이 동기적으로 이루어 진다면 프로그램이 멈추고 사용자 입력을 받아들이지도 않고 화면도 업데이트 하지 않을것이다.
비동기적 테크닉은 프로그램이 이런 식으로 멈추는 일을 막아준다.

오류 우선 콜백

콜백을 사용하면 예외 처리가 어려워지므로 콜백과 관련된 에러를 처리할 방법의 표준이 필요했다.
이에 따라 나타난 패턴이 콜백의 첫 번째 매개변수에 에러 객체를 쓰자는 것이었다.
에러가 null이나 undefined이면 에러가 없는 것입니다.
오류 우선 콜백을 다룰 때 가장 먼저 생각할 것은 에러 매개변수를 체크하고 그에 맞게 반응하는 겁니다.

노드에는 파일 콘텐츠를 읽는다고 할 때 오류 우선 콜백을 사용한다면 다음과 같다.

1
2
3
4
5
6
7
const fs = require('fs');
const fname = 'may_or_may_nor_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이 참 같은 값이라면 파일을 읽는 데 문제가 있다는 뜻이므로 콘솔에 오류를 보고하고 즉시 빠져나온다.
오류 우선 콜백을 사용할 때는 에러 객체를 체크해야 하고, 빠져 나와야 한다는 사실을 기억해야 한다.
콜백을 사용하는 함수는 대개 콜백이 성공적이라고 가정하고 만들어 진다. 그런데 콜백이 실패 했으니 빠져나가지 않으면 오류를 예약하는 것이나 다름 없다.
물론, 콜백을 만들 때 실패하는 경우도 염두에 두고 만들었다면 에러를 기록 하기만 하고 계속 진행해도 된다.
프라미스를 사용하지 않으면 오류우선 콜백은 노드 개발의 표준이나 다름 없다.

콜백 헬

콜백을 사용해 비동기적으로 실행할 수 있긴 하지만 현실적인 단점이 있다. 한 번에 여러가지를 기다려야 한다면 콜백을 관리하기가 상당히 어려워진다. 중괄호에 둘러싸여 끝없이 중청된 삼각형의 코드 블록들을 프로그래머들은 콜백 헬이라고 부른다. 가장 골치 아픈것은 에러 처리이다.

비동기적 코드가 늘어나면 늘어날수록 버그가 없고 관리하기 쉬운 코드를 작성하기는 매우 어렵다 그래서 프라미스가 등장했다.

프라미스

프라미스는 콜백의 단점을 해결하려는 시도 속에서 만들어졌다.
프라미스가 콜백을 대체하는 것은 아니다. 사실 프라미스에서도 콜백을 사용한다.
프라미스는 콜백을 예측 가능한 패턴으로 사용 할수 있게 하며, 프라미스 없이 콜백만 사용했을 때 나타날수 있는 엉뚱한 현상이나 찾기 힘든 버그를 상당수 해결한다.

프라미스의 기본 개념은 프라미스 기반 함수를 호출하면 그 함수는 Promise 인스턴스를 반환한다.
프라미스는 성공(resolve)하거나 실패(reject)하거나 단 두가지 뿐이다.
프라미스는 성공 혹은 실패 둘 중 하나만 일어난다고 확신할 수 있다.
성공한 프라미스가 나중에 실패하는 일 같은건 절대 없다. 또한 성공이든 실패든 단 한번만 일어난다.
프라미스가 성공하거나 실패하면 그 프라미스를 결정 됐다고 한다.

프라미스는 객체이므로 어디든 전달할 수 있다는 점도 콜백에 비해 간편한 장점이다.
비동기적 처리를 여기서 하지 않고 다른 함수에서 처리하게 하고 싶다면 프라미스를 넘기기만 하면 된다.

프라미스 만들기

프라미스는 쉽게 만들 수 있다.
resolve와 reject 콜백이 있는 함수로 새 Promise 인스턴스를 만들기만 하면 된다.

프라미스 사용

프라미스는 비동기적 작업이 성공 또는 실패하도록 확정하는 매우 안전하고 잘 정의된 매커니즘을 제공하지만 현재는 진행 상황을 전혀 알려주지 않는다. 즉 프라미스는 완료되거나 파기 될뿐 ‘50% 진행됐다’ 라는 개념은 아예 없다.
Q 같은 프라미스 라이브러리에는 진행 상황을 보고하는 기능이 있고 나중에 언젠가는 도입될 수도 있지만 지금 당장은 없다.

이벤트

이벤트는 자바스크립트에서 자주 사용된다. 이벤트의 개념은 간단하다.
이벤트가 일어나면 이벤트 발생을 담당하는 객체에서 이벤트가 일어났음을 알린다,
필요한 이벤트는 모두 주시 할수 있다.(콜백을 통해서)

프라미스 체인

프라미스에는 체인으로 연결 할수 있다는 장점이 있다. 즉, 프라미스가 완료되면 다른 프라미스를 반환하는 함수를 즉시 호출 할수 있다. 프라미스 체인을 사용하면 모든 단계에서 에러를 캐치할 필요는 없다. 체인 어디에서든 에러가 생기면 체인 전체가 멈추고 catch핸들러가 동작한다.

결정되지 않는 프라미스 방지하기

프라미스는 비동기적 코드를 단순화하고 콜백이 두 번 이상 실행되는 문제를 방지한다.
하지만 resolve나 reject를 호출하는 걸 잊어서 프라미스가 결정되지 않는 문제까지 자동으로 해결하지는 못한다.
에러가 일어나지 않으므로 찾기 매우 어렵다. 복잡한 시스템에서 결정되지 않은 프라미스는 그냥 잊혀질 수 있다.

결정되지 않는 프라미스를 방지하는 한 가지 방법은 타임아웃을 거는 것이다.
출분한 시간이 지났는데도 프라미스가 결정되지 않으며 자동으로 실패하게 만들 수 있다.

프라미스에 타임아웃을 거는 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function addTimeout(fn, timeout){
if(timeout === undefined) timeout = 1000; //타임아웃 기본값
return function(...args){
return new Promise(function(resolve, reject){
const tid = setTimeout(reject, timeout,
new Error("promise timed out"));
fn(...args)
.then(function(...args){
clearTimeout(tid);
resolve(...args);
})
.catch(function(...args){
clearTimeout(tid);
reject(...args)
});
});
}
}

프라미스를 반환하는 함수를 호출하는 프라미스를 반환하는 함수를 반환하는 함수 ??
프라미스에 타임아웃을 걸기 위해서는 함수를 반환하는 함수가 필요하다.

제너레이터

제너레이터는 원래 동기적인 성격을 가졌지만 프라미스와 결합하면 비동기 코드를 효율적으로 관리 할 수 있다.
비동기 코드에서 가장 어려운 부분은 동기적인 코드에 비해 만들기가 어렵다는 점이다.

프로그램에서 어떤 부분을 동시에 실행할 수 있고 또 어떤 부분은 동시에 실행 할수 없는지 판단하는 것이 중요하다.

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

제너레이터 실행기를 쓰면 try/catch를 써서 예외 처리를 할수 있다는 것도 중요한 장점이다.
콜백이나 프라미스를 사용하면 예외 처리가 쉽지 않다. 콜백에서 일으킨 예외는 그 콜백 밖에서 캐치할 수 없다.
제너레이터 실행기는 비동기적으로 실행하면서도 동기적인 동작 방식을 유지하므로 try/catch문과 함께 쓸수 있다.

요약

자바스크립트의 비동기적 실행은 콜백을 통해 이루어진다.
프라미스를 콜백 대신 사용할수 잇는건 아니다. 프라미스 역시 콜백을 사용한다.
프라미스는 콜백이 여러번 호출되는 문제를 해결했다.
콜백을 여러번 호출해야 한다면 이벤트와 결합하는 방법을 생각 할수 있다.(프라미스도 함께 쓸 수 있다.)
프라미스는 반드시 결정된다는 보장은 없다. 프라미스의 타임아웃을 걸어 이문제를 해결 할수 있다.
프라미스는 체인으로 연결할 수 있다.
프라미스와 제너레이터 실행기를 결합하면 비동기적 실행의 장점을 그대로 유지하면서도 동기적인 사고방식으로 문제를 해결할 수 있다,
제너레이터를 써서 동기적인 사고방식으로 문제를 해결할 때는 프로그래의 어느 부분을 동시에 실행 할수 있는지 잘 살펴야 한다. 동시에 실행 할수 있는 부분은 Promise.all을 써서 실행해라
제너레이터 실행기를 직점 만드는 고생을 하지말고 co나 Koa를 써라
노드 스타일 콜백을 프라미스로 바꾸는 고생도 필요없다 Q를 써라
제너레이터 실행기를 쓰면 예외 처리도 익숙한 방식으로 할 수 있다.

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×