티스토리 뷰
자바스크립트 애플리케이션은 단일 스레드에서 동작합니다. 즉 자바스크립트는 한 번에 한 가지 일만 할 수 있습니다.
자바스크립트는 비동기적 프로그래밍에 대처하기 위해 처음에는 콜백을, 다음에는 프라미스 마지막은 제너레이터를 추가시켰습니다.
각 방법에 대해 들어가기 전 이해가 쉽도록 비유적인 표현을 책에서 명시하고 있습니다.
예약하지 않고 분주한 음식점에 방문한 경우, 콜백이란 음식점은 줄을 서서 기다리지 않도록 당신의 전화번호를 받아서 자리가 나면 전화해줍니다. 프라미스라는 음식점의 경우는 자리가 났을 때 진동하는 호출기를 당신에게 넘겨줍니다. 실행되어야 하는 타이밍을 알려주는 수단을 콜백의 경우 당신이 음식점에게, 프라미스의 경우 음식점이 당신에게 준다는 비유를 명시하면 좀 더 쉽게 이해할 수 있습니다.
콜백
콜백은 간단히 말해 나중에 호출할 함수입니다. 콜백함수 자체에는 특별할 것이 없고 일반적인 JS의 함수일 뿐입니다. 콜백함수는 일반적으로 다른 함수에 넘기거나 객체의 프로퍼티로 사용합니다.
/스코프와 비동기적 실행
함수를 호출하면 클로저가 만들어지고 매개변수를 포함해 함수 안에서 만든 변수는 무언가가 자신에 접근할 수 있는 한 계속 존재합니다.
function countdown() {
let i;
console.log("Countdown");
for(i=5;i>=0;i--){
setTimeout(function() {
console.log(i === 0 ? "Go" : i);
}, (5-i)*1000);
}
}
이 예제의 결과는 5초부터 카운트다운 하기를 바라던 예상과는 달리 -1만 여섯 번 반복됩니다. 변수가 for 루프 밖에서 선언됬기 때문에 for루프가 실행을 마치고 i의 값이 -1이 된 후에야 콜백이 시작됩니다. 이 문제는 간단히 클로저 내부에서 변수를 선언함으로서 해결할 수 있습니다. 이 예제를 통해 비동기적 코드를 사용할 때 콜백이 어느 스코프에 선언됬는지 확인해볼 필요에 대해 알 수 있습니다.
/오류 우선 콜백
콜백을 사용하면 예외 처리가 어려워지기 때문에(차후 설명) 이에 대한 해결방법으로 콜백의 첫 번째 매개변수에 에러 객체를 쓰는(에러가 null이나 undefined이면 에러가 없는 것) 오류 우선 콜백이라는 패턴이 나타났습니다.
const fs = require('rs');
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}`);
}
}
에러가 있다면 빠져나오기 위해 로그를 찍고 리턴합니다.
/콜백 헬
콜백을 사용해 비동기적으로 실행할 수 있지만 한 번에 여러가지를 기다려야 한다면 콜백을 관리하기 어려워집니다. 예를 들어 A파일의 콘텐츠를 읽으면 B콘텐츠를 읽으면 C콘텐츠를.... 이렇게 반복적인 비동기작업이 필요하다면 끝없는 중괄호에 둘러쌓이게 될 것입니다.
다른 문제로는 직관적이지 못한 예외처리입니다.
const fs = require('fs');
function readSketchFile() {
try {
fs.readFile('does_not_exist.txt', function(err, data){
if(err) throw err;
});
} catch(err) {
console.log("error");
}
}
readSketchFile();
try문에서 에러를 throw해줘서 catch문에서 정상적인 예외처리가 될 것 같지만 그렇지 않습니다. try...catch 블록은 같은 함수 안에서만 동작합니다. fs.readFile이 콜백으로 호출하는 익명함수에서 예외가 일어났기 때문에 readSketchFile 함수안에 있는 try..catch문은 예외처리를 할 수 없습니다.
프라미스
프라미스는 위 언급한 문제점을 해결하기 위해 만들어졌습니다. 프라미스가 콜백을 대체하는 것은 아닙니다. 프라미스에서도 콜백을 사용합니다. 다만 콜백을 예측가능한 패턴으로 사용할 수 있게 하며 콜백만 사용했을시의 문제점을 상당수 해결합니다.
프라미스는 프라미스 기반 비동기적 함수를 호출하면 그 함수는 Promis 인스턴스를 반환합니다. 프라미스는 성공하거나(fullfilled) 실패하거나(rejected) 두 가지 뿐입니다. 프라미스가 성공하거나 실패하면 그 프라미스를 결정됐다"(settled) 합니다.
프라미스는 객체임으로 어디든 전달할 수 있습니다. 비동기적 처리를 다른 함수에서 처리하고 싶다면 프라미스를 넘기기만 하면 됩니다.
function countdown(seconds){
return new Promise(function(resolve, reject) {
for(let i = seconds; i >= 0; i--){
setTimeout(function(){
if(i>0) console.log(i + '...');
else resolve(console.log("GO!"));
}, (seconds-i)*1000);
}
});
}
/프라미스 사용
위 예제를 통해 프라미스를 사용하는 방법을 알아봅시다.
countdown(5).then(
function(){
console.log("countdown completed succesfully");
},
function(err){
console.log("countdown experienced an error: " + err.message);
}
);
위 예제는 반환된 프라미스를 변수에 할당하지 않고 then 핸들러를 바로 호출했습니다. then 핸들러는 성공 콜백과 에러 콜백을 받습니다.
프라미스는 catch 핸들러도 지원하기 때문에 핸들러를 둘로 나눠서 써도 됩니다.
...
p.catch(function(err){
console.log("countdown experienced an error: " + err.message);
});
프라미스는 비동기적 작업이 성공 또는 실패하도록 확정하는 메커니즘을 제공하지만 진행상황을 알려주진 않습니다. 진행사항을 알기 위해서는 이벤트에 대해 알아볼 필요가 있습니다. 예제가 백엔드 중점으로 서술되어 있기 때문에 이부분은 생략하겠습니다.
/프라미스 체인
프라미스에는 체인으로 연결할 수 있다는 장점이 있습니다. 즉, 프라미스가 완료되면 다른 프라미스를 반환하는 함수를 즉시 호출할 수 있습니다.
'Running JS 요약' 카테고리의 다른 글
JS - 함수와 추상적 사고 (0) | 2021.01.21 |
---|---|
JS - 이터레이터와 제너레이터 (0) | 2021.01.20 |
JS - 맵과 셋 / 예외와 에러 처리 (0) | 2021.01.18 |
JS - 객체와 객체지향 프로그래밍 (0) | 2021.01.11 |
JS - 배열 (0) | 2021.01.06 |