우선 차이점부터 설명하자면, 동기는 '직렬적'으로 작동하는 방식이고 비동기는 '병렬적'으로 작동하는 방식이다. 즉, 비동기란 특정 코드가 끝날때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것을 의미한다. 비동기 처리를 예로 Web API, Ajax, setTimeout 등이 있다.
아래 사진을 통해 차이점을 한눈에 볼 수 있다.
이미지 출처: What every programmer should know about Synchronous vs. Asynchronous Code
이미지 출처: 동기식 처리 모델(Synchronous processing model)
step1(function(value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
step5(value4, function(value5) {
// value5를 사용하는 처리
});
});
});
});
});
// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
// 비동기 작업을 수행한다.
if (/* 비동기 작업 수행 성공 */) {
resolve('result');
}
else { /* 비동기 작업 수행 실패 */
reject('failure reason');
}
});
Promise는 비동기 처리가 성공(fulfilled)하였는지 또는 실패(rejected)하였는지 등의 상태(state) 정보를 갖는다.
pending
: 비동기 처리가 아직 수행되지 않은 상태fulfilled
: 비동기 처리가 수행된 상태 (성공)rejected
: 비동기 처리가 수행된 상태 (실패)settled
: 비동기 처리가 수행된 상태 (성공 또는 실패)후속 처리 메소드에는 대표적으로 then
(Promise 반환)과 catch
(예외)가 있다.
then
then 메소드는 두 개의 콜백 함수를 인자로 전달 받는다. 첫 번째 콜백 함수는 성공(fulfilled, resolve 함수가 호출된 상태) 시 호출되고 두 번째 함수는 실패(rejected, reject 함수가 호출된 상태) 시 호출된다.then 메소드는 Promise를 반환한다.
catch
예외(비동기 처리에서 발생한 에러와 then 메소드에서 발생한 에러)가 발생하면 호출된다. catch 메소드는 Promise를 반환한다.
비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩(nesting)이 되어 복잡도가 높아지는 콜백 헬이 발생한다. 프로미스는 후속 처리 메소드인 then
이나 catch
로 메소드를 체이닝(chainning)하여 여러 개의 프로미스를 연결하여 사용할 수 있다. 이로써 콜백 헬을 해결한다.
따라서, then 메소드가 Promise 객체를 반환하도록 하면(then 메소드는 기본적으로 Promise를 반환한다.) 여러 개의 프로미스를 연결하여 사용할 수 있다.
// 포스트 id가 1인 포스트를 검색하고 프로미스를 반환한다.
promiseAjax('GET', `${url}/1`)
// 포스트 id가 1인 포스트를 작성한 사용자의 아이디로 작성된 모든 포스트를 검색하고 프로미스를 반환한다.
.then(res => promiseAjax('GET', `${url}?userId=${JSON.parse(res).userId}`))
.then(JSON.parse)
.then(render)
.catch(console.error);
async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다. 특히, 복잡했던 Promise를 조금 더 편하게 사용할 수 있다. async await 의 기본 문법은 아래와 같다.
async function 함수명() {
await 비동기_처리_메서드_명();
}
async function f() {
return 1;
}
f().then(alert); // 1
위 함수에서 1을 Promise.resolve
로 감싸도 같은 결과를 반환한다.
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
즉, async
가 붙은 함수는 반드시 프라미스를 반환하고, 프라미스가 아닌 것은 프라미스로 감싸 반환한다.
await는 async 함수 안에서만 동작한다. await는 ‘기다리다'라는 뜻을 가진 영단어 인데, 프라미스가 처리될 때 까지 기다리는 역할을 한다. 그리고 결과는 그 이후 반환된다.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)
alert(result); // "완료!"
}
f();
(*)
로 표시한 줄에서 실행이 잠시 '중단’되었다가 프라미스가 처리되면 실행이 재개된다. result
값이 변수 result에 할당된다. 따라서 위 예시를 실행하면 1초 뒤에 '완료!'가 출력된다. await
는 말 그대로 프라미스가 처리될 때까지 함수 실행을 기다리게 만든다. 프라미스가 처리되면 그 결과와 함께 실행이 재개된다. 프라미스가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.
await
를 사용하지 않았다면 데이터를 받아온 시점에 콘솔을 출력할 수 있게 콜백 함수나 .then()
await
는 promise.then
보다 좀 더 세련되게 프라미스의 result
값을 얻을 수 있도록 해주는 문법이다. promise.then
보다 가독성 좋고 쓰기도 쉽다.async await 를 사용한 깃헙의 프로필 사진을 보여주는 코드 예시이다
async function showAvatar() {
// JSON 읽기
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// github 사용자 정보 읽기
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// 아바타 보여주기
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// 3초 대기
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
await
가 던진 에러는 throw
가 던진 에러를 잡을 때처럼 try..catch
를 사용해 잡을 수 있다.
async function f() {
try {
let response = await fetch('http://유효하지-않은-주소');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
에러가 발생하면 제어 흐름이 catch 블록으로 넘어간다. 또한, 여러 줄의 코드를 try로 감쌀 수 있다.
async/await
을 사용하면 await
가 대기를 처리해주기 때문에 .then
이 거의 필요하지 않다. 또한, .catch
대신 일반 try..catch
를 사용할 수 있다는 장점도 있다. 항상 그러한 것은 아니지만, promise.then
을 사용하는 것보다 async/await
를 사용하는 것이 대개는 더 편리하다
그런데 문법 제약 때문에 async
함수 바깥의 최상위 레벨 코드에선 await
를 사용할 수 없다. 그렇기 때문에 관행처럼 .then/catch
를 추가해 최종 결과나 처리되지 못한 에러를 다룬다.
글 잘보았습니다!
써주신 포스팅이 깔끔하고 핵심정보가 많아서, 이해하고 싶어 몇번을 다시 곱씹어 읽고
나중에 다시 보려고 링크도 저장해놓았네요 ㅎㅎ
좋은 글 감사합니다!