웹 애플리케이션을 만들다 보면 시간이 걸리는 작업들이 있다.
예를들면 서버 쪽 데이터가 필요할 때는 Ajax 기법을 이용하여 서버의 API를 호출함으로써 데이터를 수신한다.
이렇게 서버의 API를 사용해야 할 때는 네트워크 송수신 과정에서 시간이 걸리기 때문에 작업이 즉시 처리되는 것이 아니라, 응답을 받을 때까지 기다렸다가 전달받은 응답 데이터를 처리한다. 그리고 이 과정에서 해당하는 작업들을 비 동기적으로 처리하게 된다.
만약 작업을 동기적으로 처리한다면 요청이 끝날 때까지 기다리는 동안 중지 상태가 되기 때문에 다른 작업을 할 수가 없다. (한 작업 끝나면 그다음 작업, 또 한 작업 끝나면 그다음 작업 이런 식으로 수행)
하지만 비 동기적으로 작업을 처리한다면 중지 상태가 되지 않기 때문에 동시에 여러 가지 요청을 처리할 수도 있고, 기다리는 과정에서 다른 함수를 호출 할 수도 있다.
서버 API를 호출할 때 외에도 작업을 비동기적으로 처리 할 때가 있는데, setTimeout 함수를 사용하여 특정 작업을 예약할 때 이다.
function printMe() {
console.log("Hello World");
}
setTimeout(printMe, 3000);
console.log("대기중");
// -- result --
// 대기중
// Hello World
setTimeout이 사용되는 시점에서 코드가 3초 동안 멈추는 것이 아니라, 일단 코드가 위부터 아래까지 다 호출되고 3초 뒤에 printMe 함수가 호출된다.
비동기 작업을 할 때 가장 흔히 사용하는 방법은 콜백 함수를 사용하는 것이다.
(위 코드에서 printMe가 3초 뒤에 호출되도록 printMe 함수 자체를 setTimeout 함수의 인자로 전달해 주었는데, 이런 함수를 콜백 함수라고 부른다.)
콜백 함수
function increase(number, callback) {
setTimeout(() => {
const resultNum = number + 10;
if (callback) {
callback(resultNum);
}
}, 1000);
}
increase(0, (result) => {
console.log(result);
});
파라미터 값이 주어지면 1초뒤에 10을 더해서 반환하는 함수가있고, 해당 함수가 처리된 직후 어떠한 작업을 하고 싶다면 위 처럼 콜백 함수를 활용해서 작업 해야한다.
1초에 걸쳐서 10, 20, 30, 40과 같은 형태로 여러 번 순차적으로 처리하고 싶다면 콜백 함수를 중첩하여 구현할 수도있다.
function increase(number, callback) {
setTimeout(() => {
const resultNum = number + 10;
if (callback) {
callback(resultNum);
}
}, 1000);
}
console.log("작업시작");
increase(0, (result) => {
console.log(result);
increase(result, (result) => {
console.log(result);
increase(result, (result) => {
console.log(result);
increase(result, (result) => {
console.log(result);
console.log("작업 완료");
});
});
});
});
이런식으로 콜백 안에 또 콜백을 넣어서 구현할 수 있는데, 너무 여러 번 중첩되서 코드의 가독성이 좋지않다.
이러한 형태의 코드를 "콜백 지옥" 이라고 부르며, 지양해야 할 형태의 코드이다.
Promise
Promise 는 콜백 지옥 같은 코드가 형성되지 않게 하는 방안으로 ES6에 도입된 기능이다.
위에서 Callback으로 구현한 코드를 Promise를 이용하여 구현하면 다음과 같다.
function increase(number) {
const promise = new Promise((resolve, reject) => {
// resolve는 성공, reject는 실패
setTimeout(() => {
const resultNum = number + 10;
if (resultNum > 50) {
// 50 보다 높으면 에러 발생
const e = new Error("NumberTooBig");
return reject(e);
}
resolve(resultNum);
}, 1000);
});
return promise;
}
increase(0)
.then((number) => {
// promise에서 resolve된 값은 .then으로 받아 올 수 있음
console.log(number);
return increase(number); // promise를 리턴하면
})
.then((number) => {
// 또 then으로 처리 가능
console.log(number);
return increase(number);
})
.then((number) => {
console.log(number);
return increase(number);
})
.then((number) => {
console.log(number);
return increase(number);
})
.catch((e) => {
// 도중에 에러가 발생한다면 .catch를 통해 알 수 있음
console.log(e);
});
여러 작업을 연달아 처리한다고 해서 함수를 여러 번 감싸는 것이 아니라 .then을 사용하여 그 다음 작업을 설정하기 때문에 콜백 지옥이 형성되지 않는다.
async / await
async / await 는 Promise를 더욱 쉽게 사용할 수 있도록 해 주는 ES8 문법이다. 이 문법을 사용하려면 함수의 앞부분에 async 키워드를 추가하고, 해당 함수 내부에서 Promise 앞 부분에 await 를 사용한다. 이렇게 하면 Promise 가 끝날 때까지 기다리고, 결과 값을 특정 변수에 담을 수 있다.
function increase(number) {
const promise = new Promise((resolve, reject) => {
// resolve는 성공, reject는 실패
setTimeout(() => {
const resultNum = number + 10;
if (resultNum > 50) {
// 50 보다 높으면 에러 발생
const e = new Error("NumberTooBig");
return reject(e);
}
resolve(resultNum);
}, 1000);
});
return promise;
}
async function runTasks() {
try {
let result = await increase(0);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
} catch (e) {
console.log(e);
}
}
runTasks();
* velopert 님의 리액트를 다루는 기술에 나오는 내용을 참고하였습니다. *
'프로그래밍 > JavaScript' 카테고리의 다른 글
JS - Array.from() : Array-like Object(유사 배열) 를 진짜 Array로 바꿔주는 메소드 (0) | 2021.09.17 |
---|---|
JS - 배열을 삭제,삽입,수정할 때 강력한 메서드 splice() (0) | 2021.09.04 |
JS - 자바스크립트 함수 매개변수에 대한 이해 (0) | 2021.09.03 |
자바스크립트 배열 관련 함수 (0) | 2021.08.18 |
자바스크립트 - map() 함수? (0) | 2021.08.16 |