동기와 비동기
동기 실행의 경우 한 작업이 다 끝나면 다음작업을 진행한다. 코드를 알아보기 쉽다는 장점이 있지만, 웹페이지 특성상 여러가지를 로딩해야 하는 경우가 많은데, 작업 하나하나를 끝날 때 까지 기다렸다가 진행하기 때문에 시간이 오래 걸린다는 단점이 있다.
반면, 비동기 실행의 경우 작업을 진행하다가 시간이 걸리는 작업을 만나면(데이터 로딩, 서버에 요청과 응답 등) 작업완료를 기다리지 않고 바로 다음작업을 진행하여 여러 작업을 동시에 진행하기 때문에 동기 실행에 비해 시간이 적게 걸린다.
Javascript는 싱글 스레드를 기반으로 동작하지만, 작동하는 환경(런타임)에서 비동기 처리를 도와주기 때문에 특별한 작업 없이 비동기 처리를 할 수 있다.
타이머 관련 API
Javascript에서 가장 처음으로 만나는 비동기인 타이머 API에는 setTimeout, setInterval 등이 있다.
setTimeout(function () {
console.log('1초 후 실행');
}, 1000); // 1000ms(1초) 이후 '1초 후 실행' 한 번 출력
setInterval(function () {
console.log('1초마다 실행');
}, 1000);
// 1000ms(1초) 마다 '1초마다 실행' 출력
비동기 함수로 코드를 작성하면 코드의 순서를 예측하기 힘들다.
const printString = (string) => {
setTimeout(function () {
console.log(string);
}, Math.floor(Math.random() * 100) + 1);
};
const printAll = () => {
printString('A');
printString('B');
printString('C');
};
printAll(); // 코드를 실행할 때 마다 실행 순서가 달라진다.
콜백함수를 이용한 비동기 제어
콜백 함수를 사용하면 비동기 함수를 예측 가능하도록 만들 수 있다.
const printString = (string, callback) => {
setTimeout(function () {
console.log(string);
callback();
}, Math.floor(Math.random() * 100) + 1);
};
const printAll = () => {
printString('A', () => {
printString('B', () => {
printString('C', () => {});
});
});
};
이 코드에서는 각각 실행시간은 실행할 때 마다 달라지지만, 콜백함수를 통해 A출력 이후 B 출력, B출력 이후 C 출력을 하는 순서는 변하지 않는다. 이렇게 콜백 함수를 사용하면 비동기 코드의 실행 순서를 제어할 수 있다.
콜백함수를 너무 남발하면 콜백 지옥(callback hell)에 빠질 수 있다.
Promise 제어
비동기 코드를 제어하는 또다른 방법으로 Promise를 활용할 수 있다.
let promise = new Promise((resolve, reject) => {
// 1. 정상적으로 처리되는 경우
// resolve의 인자에 값을 전달할 수도 있습니다.
resolve(value);
// 2. 에러가 발생하는 경우
// reject의 인자에 에러메세지를 전달할 수도 있습니다.
reject(error);
});
executor(콜백 함수)에 작성했던 코드들이 정상적으로 처리가 되었다면 resolve 함수를 호출하고 .then 메서드로 접근할 수 있다. 또한 .then 안에서 리턴한 값이 Promise면 Promise의 내부 프로퍼티 result를 다음 .then 의 콜백 함수의 인자로 받아오고, Promise가 아니라면 리턴한 값을 .then 의 콜백 함수의 인자로 받아올 수 있다.
executor에 작성했던 코드들이 에러가 발생했을 경우에는 reject 함수를 호출하고 .catch 메서드로 접근할 수 있다.
executor에 작성했던 코드들의 정상 처리 여부와 상관없이 .finally 메서드로 접근할 수 있다.
let promise = new Promise(function(resolve, reject) {
resolve("성공");
});
promise
.then(value => {
console.log(value);
// "성공"
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log("성공이든 실패든 작동!");
// "성공이든 실패든 작동!"
})
.then, .catch, .finally 메서드가 Promise 객체를 반환하기 때문에 Promise chaining이 가능하다.
promise
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.catch((error) => {
console.log(error);
return '실패';
})
.finally(() => {
console.log('성공이든 실패든 작동!');
});
여러 개의 비동기 작업을 동시에 처리하고 싶을때 Promise.all()을 사용한다.
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
.then((value) => console.log(value))
// ['1초', '2초', '3초']
.catch((err) => console.log(err));
Promise chain 도 콜백함수와 마찬가지로 너무 남발하면 Promise then지옥에 빠진다.
Async/ Await
ES8에서 추가된 async/await 키워드를 사용하면 복잡한 Promise 코드를 간결하게 작성할 수 있다.
함수 앞에 async 키워드를 사용하고 async 함수 내에서만 await 키워드를 사용하면 된다.
이렇게 작성된 코드는 await 키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작하게 된다.
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(string);
}, Math.floor(Math.random() * 100) + 1);
});
};
const printAll = async () => {
await printString('A');
await printString('B');
await printString('C');
};
printAll();
'코드스테이츠' 카테고리의 다른 글
1/19 일일정리 비동기 실습 (1) | 2023.01.19 |
---|---|
1/18 일일정리 타이머API, fs 모듈 (0) | 2023.01.18 |
1/17 일일정리(1) Underbar (0) | 2023.01.17 |
1/16 일일정리 프로토타입 (0) | 2023.01.16 |
1/13 일일정리 클래스와 인스턴스 (0) | 2023.01.13 |
댓글