동기적 처리
코드의 실행이 끝날때까지 기다렸다 끝나면 다음 코드가 실행되는 것
비동기적 처리
코드의 실행이 끝날때 까지 기다리지 않고 다음 코드가 실행되는 것
자바스크립트는싱글스레드 런타임을 가진 동기식 언어이다.
프로세스? 스레드?
프로세스란
운영체제로부터 자원을 할당받는 작업의 단위로 디스크로부터 메모리에 적재되어 운영체제로부터 주소 공간, 파일, 메모리 등을 할당받으며 이것들을 총칭하여 프로세스라고 한다.스레드란
프로세스가 할당받은 자원을 이용하는 실행의 단위로 싱글 스레드와 멀티 스레드가 존재한다.싱글 스레드는 하나의 프로세스에서 하나의 스레드를 가지기 때문에 하나의 작업만 수행할 수 있고, 멀티 스레드는 한번에 여러 개의 작업을 수행할 수 있다.
하지만 자바스크립트는 브라우저에서 비동기적으로 코드를 처리할 수 있다.
그 이유는 브라우저에 내장되어 있는 이벤트 루프(Event Loop)
때문에 멀티 스레드처럼 동작해 비동기 작업이 가능해진 것이다.
비동기 처리는 현재 코드의 실행이 완료되지 않더라도 다음 코드를 실행하는 방식을 말한다.
동시에 여러 코드를 실행할 수 있다는 장점이 있지만, 비동기 함수가 많을 경우 어떤 코드가 먼저 실행되는지 알 수 없고, 가독성도 떨어진다.
이러한 문제를 해결하기 위해 비동기식 프로그래밍 패턴이 생겼고 그 패턴에 대해 알아보자.
콜백 함수란 함수의 전달인자로 다른 함수를 넘겨주는 것을 말한다.
모든 콜백 함수가 비동기식 처리를 따르는 것은 아니다.(Map
,filter
등은 동기식으로 사용됨. )
비동기식으로 코드를 처리하는 내장 함수들이 따로 있는데 그것은 바로 setTimeout
,fetch()
가 있다.
이 내장 함수들을 사용하면 비동기로 작동하는 코드의 순서들을 제어할 수 있다. ( 비동기를 동기화할 수 있다는 의미.)
const printString = (string, callback) => {
setTimeout(function () {
console.log(string);
callback();
}, Math.floor(Math.random() * 100) + 1);
};
const printAll = () => {
printString('A', () => {
printString('B', () => {
printString('C', () => {});
});
});
};
printAll();
console.log(
`아래와 같이 Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있습니다!`
);
// 실행 순서
아래와 같이 Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있습니다!
A
B
C
하지만 콜백함수를 많이 사용할수록 코드가 복잡해지고 가독성이 떨어진다는 단점이 있다. 이를 콜백지옥 이라 부른다.
Promise
는 Callback Hell
을 방지하는 역할과 비동기로 작동하는 코드를 제어할 수 있는 또다른 방식이다.
Promise
는 class 라서 new 키워드를 통해 promise 객체를 생성한다.
let promise = new promise ((resolve,reject)=>{...}
객체를 생성할때 new promise 의 전달인자로 콜백함수를 전달받는데 (executor) 이 콜백함수의 전달인자로는 resolve
, reject
함수를 전달 받는다. (함수(new promise)
안에 함수(excutor)
안에 함수(resolve, reject)
)
promise 는 대기, 이행, 거부 3가지 상태로 구분된다.
resolve
함수가 호출된다. reject
함수가 호출된다.let promise = new Promise((resolve, reject) => { // 대기 상태 (Pending)
resolve(value); // 이행 상태 (Fulfilled)
reject(error); // 거부 상태 (Rejeted)
});
promise 를 사용해 비동기 처리 이후 변경된 상태(이행,거부)에 접근해 값을 사용하기 위한 메소드가 있다.
let promise = new Promise((resolve, reject) => { // 대기 상태 (Pending)
resolve(value); // 이행 상태 (Fulfilled)
reject(error); // 거부 상태 (Rejeted)
});
promise
.then(data => console.log(data))
.catch(err => console.error(err))
.finally(()=> console.log('always run')
후속 처리 메서드를 사용하게 되면 새로운 프로미스 객체
가 반환되고 이것들을 체이닝하여 사용할 수 있다.
promise 의 기본 data === 1000
then()
을 통해 +1000 을 더한 값을 새로운 프로미스 객체로 생성 , 이후 또then()
을 활용해 값 변경 할 수 있다. (체이닝)
Promise all 메소드는 프로미스가 담겨 있는 배열을 인자로 받고, 전달 받은 모든 프로미스를 병렬로 처리해 그 처리 결과를 resolve
하는 새로운 프로미스를 반환한다.
Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 ]).then(console.log) // [ 1, 2, 3 ] .catch(console.log);
- 첫번째 프로미스는 3초 후에 1을 resolve하여 처리 결과를 반환
- 두번째 프로미스는 2초 후에 2을 resolve하여 처리 결과를 반환
- 세번째 프로미스는 1초 후에 3을 resolve하여 처리 결과를 반환
첫번째 프로미스가 가장 나중에 처리되어도 Promise.all 메소드가 반환하는 프로미스는 첫번째 프로미스가 resolve한 처리 결과부터 차례대로 배열에 담기 때문에 처리 순서가 보장된다.
프로미스의 처리가 하나라도 실패하면 거부 상태로 reject
메소드 통해 새로운 프로미스를 즉시 반환한다.
콜백 지옥 처럼 프로미스의 then()
메서드를 연쇄적으로 체이닝하다보면 가독성이 떨어지고 복잡성이 높아지게 된다.
promise 의 then() 메소드를 연쇄적으로 체이닝하다보면 생기는 promise hell
을 해결하기 위해 만들어진 게 async/await
이다.
기존 promise 를 보다 간결하게 작성할 수 있게 되고, 비동기적 코드를 동기적인 코드인 것 처럼 직관적으로 바꿔주는 역할을 한다. -> 비동기 코드에 순서를 부여
async
는 function 키워드 앞에 적고 await
은 비동기로 처리되는 부분 앞에 작성하면 된다.// 기본 구조
async function() {
await
}
async function asyncFunc() {
let response = await fetch('#');
let data = await response.json();
return data;
}
프로미스와의 차이점
// 프로미스 객체 반환 함수
function delay(ms) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`${ms} 밀리초가 지났습니다.`);
resolve()
}, ms);
});
}
// 기존 Promise.then() 형식
function main() {
delay(1000)
.then(() => {
return delay(2000);
})
.then(() => {
return Promise.resolve('끝');
})
.then(result => {
console.log(result);
});
}
// 메인 함수 호출
main();
// async/await 방식
async function main() {
await delay(1000);
await delay(2000);
const result = await Promise.resolve('끝');
console.log(result);
}
// 메인 함수 호출
main();
프로미스의 경우에는 then 메서드를 연속적으로 체이닝하여 비동기 처리를 하고 있지만 async/await 의 경우에는 await 키워드를 사용해 비동기 처리를 하고 있다는 걸 알 수 있다.
이처럼 async/await
을 사용하면 비동기적 접근방식을 동기적으로 작성할 수 있게 해주어 코드가 간결해지며, 가독성을 높이고 유지보수를 용이하게 해준다.