Etc/Frontend

[JavaScript] async & await란?

z.zzz 2023. 3. 23. 17:42

Callback & Promise

1초마다 로그를 출력하는 동일한 내용의 코드를 콜백함수와 프로미스로 구현했다.

프로미스를 사용함으로서 콜백함수가 중첩되는 현상인 '콜백 지옥'로부터 벗어났다.

// callback hell
timer(1000, function() {
  console.log("작업");
  timer(1000, function() {
    console.log("작업");
    timer(1000, function() {
      console.log("작업");
    });
  });
});

// use promise's then
timer(1000)
  .then(function() {
    console.log("작업");
    return timer(1000);  // 타이머 실행 후 Promise 객체를 return함
  })
  .then(function() {
    console.log("작업");
    return timer(1000);
  })(function() {
    console.log("작업");
  })

 

await & async

이제 프로미스를 써서 콜백 지옥으로 부턴 벗어났다. 근데 더 읽기 좋은 코드를 만들기 위해 then, function, return도 안 쓰고싶다. 마치 동기적인 코드를 작성하는 것처럼 비동기 코드를 쓸 수 있는 방법은 없을까? 이때 쓸 수 있는 게 await와 async다. 

 

 

await & async 코드 만들기

1. 비동기 함수 앞에 '이 함수가 실행되길 기다려라'는 뜻의 await를 붙인다.

2. await가 붙은 promise를 반환하는 함수는 반드시 다른 함수 안에서 실행돼야 하며, 그 함수 앞엔 async 키워드를 붙인다.

async function run() {
  await timer(1000)
  console.log("작업");
  
  await timer(1000)
  console.log("작업");
  
  await timer(1000)
  console.log("작업");
}
run();

이렇게하면 위 코드와 똑같이 동작하는 코드를 깔끔하게 짤 수 있다.

 

await의 return 값 사용하기

function timer(time) {
  return new Promise(function(resolve) {  // 프로미스 생성은 일단 black box로 생각한다
    setTimeout(function(){
      resolve(time);
    }, time);
  });
}

// promise code
console.log('start');
timer(1000).then(function(time){
  console.log("time:"+time)
  return timer(time+1000);
}).then(function(time){
  console.log("time:"+time)
  return timer(time+1000);
}).then(function(time){
  console.log("time:"+time)
  // console.log('end');  // start -> time:1000 -> time:2000 -> time:3000 -> end
});
console.log('end');  // start -> end -> time:1000 -> time:2000 -> time:3000

start 로그를 찍고 타이머 함수 실행 후 마지막에 end 로그를 찍고싶지만 현재 코드에선 start 로그, end 로그가 출력된 후 타이머 함수가 실행된다. end 로그를 마지막에 찍으려면 마지막 time 출력 코드 뒤에 end 로그가 있어야 한다. (line 18)

 

위 코드는 가독성이 떨어지기 때문에 await와 async로 코드를 정리해보면 다음과 같다.

1. timer 함수가 비동기적 코드임을 명시하기 위해 await를 붙인다.

2. await를 붙이면 비동기 함수(timer)의 결괏값(time)을 변수로 받을 수 있다.

3. await 함수를 감싸는 바깥 함수에 async를 붙인다.

// await & async code
async function run() {
  console.log('start');
  var time = await timer(1000);
  console.log("time:"+time);
  time = await timer(time+1000);
  console.log("time:"+time);
  time = await timer(time+1000);
  console.log("time:"+time);
  console.log('end');
  return time
}
console.log(run());  // start -> time:1000 -> time:2000 -> time:3000 -> end

 

중첩된 await & async

console.log('parent start');
run();
console.log('parent end');  
// parent start -> start -> parent end -> time:1000 -> time:2000 -> time:3000 -> end

parent start와 parent end 로그를 출력하고 싶지만, async는 비동기적으로 동작하는 함수기 때문에 parent end가 마지막에 출력되지 않는다. parent end를 마지막에 출력하려면 다음과 같이 바꾼다.

async function run2() {
  console.log('parent start');
  await run();  // return Promise { <pending> }
  console.log('parent end');
}
run2();  // parent start -> start -> time:1000 -> time:2000 -> time:3000 -> end -> parent end

run 함수는 비동기 함수(async)여서 promise를 리턴하기 때문에 앞에 await를 붙일 수 있다. 그리고 다시 함수로 묶어주면 parent end를 마지막에 출력할 수 있다.

 

console.log('parent parent start');
run2()
console.log('parent parent end');  
// parent parent start -> parent start -> start -> parent parent end -> time:1000 -> time:2000 -> time:3000 -> end -> parent end

이번엔 parent parent start와 parent parent end를 출력하고 싶은데 위와 같은 코드면 parent parent end가 마지막에 출력되지 않는다. 따라서 다음과 같이 수정한다. 

console.log('parent parent start');
run2().then(function(){
  console.log('parent parent end');  
})
//parent parent start -> parent start -> start -> time:1000 -> time:2000 -> time:3000 -> end -> parent end -> parent parent end

최상위에선 async를 쓰지 않아도 되니까 then으로 처리한다.

 

 

 

더보기

해당 게시글은 생활코딩님의 JavaScript - async & await 강의를 참고해 작성했습니다.