티스토리 뷰

ES8

비동기 프로그래밍으로 발생하는 콜백지옥을 해결하는 방법으로 ES6에서 추가된 Promise를 사용하는 방법이 있습니다. ([자바스크립트] 비동기프로그래밍 - 콜백함수(Callback function) 참고) 이번 포스트에서는 비동기 프로그래밍을 처리할 수 있는 방법으로 asyncawait에 대해 이야기 하려 합니다.

1. 문법

async function name([param[, param[, ... param]]]) { 
    statements 
}
  • name: 함수 이름
  • param: 함수에 전달되는 인자들의 이름
  • statements: 함수 본문
  • 리턴 값: async 함수의 return 값으로 resolvePromise 객체 혹은 async 함수에서 예외가 발생하였다면 발생 된 예외로 rejectPromise 객체

async 함수를 사용하는 방법은 기본 함수를 생성하는 것과 큰 차이가 없습니다. 그렇기 때문에 Promise를 이용하는 것보다 async, await를 사용할 때 코드가 더 간결해 질 수 있습니다. async 함수의 리턴 값은 resolvePromise 객체, 혹은 rejectPromise 객체를 암묵적으로 리턴합니다. 암묵적으로 리턴한다는 말은, new Promise 를 사용하여 Promise 객체를 리턴하지 않아도 async 함수의 리턴 값으로 Promise 객체를 받게 된다는 뜻으로 사용하였습니다.

* 참고 - 리턴 값 확인하기

return 값 확인하기 - resolve
async 함수 안에서 리턴 한 값은 resolve 된 Promise 객체로 리턴 되어 집니다.

return 값 확인하기 - reject
async 함수 안에서 발생한 예외는 reject 된 Promise 객체로 리턴 되어 집니다.

2. 데모

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  var result = await resolveAfter2Seconds();
  console.log(result);
  // expected output: 'resolved'
}

asyncCall();
 

3. 설명

async 함수에서는 await를 사용할 수 있습니다. await는 async 함수에서만 사용 가능합니다. 일반 함수에서 await를 사용하게 되면 syntax error가 발생됩니다.

awaitPromise와 함께 사용되어야 합니다. await를 사용하면 Promise가 종료 될 때까지 함수 실행이 일시 정지 됩니다. 그후 Promise가 종료 되면 함수 실행이 다시 진행 됩니다. await 사용하면 Promise에서 resolve 된 값을 반환 받게 됩니다. awaitPromisereject 되면, 예외가 발생됩니다.

function awaitFunction() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve('success'), 1000);
    // setTimeout(() => reject('fail'), 1000);
  });
}

async function asyncFunction() {
  try {
    const msg = await awaitFunction();
    console.log(msg); // awaitFunction에서 resolve가 호출 될 때 resolve의 인자값 'success'
  } catch (e) {
    console.log(e); // awaitFunction에서 reject가 호출 될 때 reject의 인자값 'fail'
  }
}

asyncFunction();

Promise의 사용 방법을 단순히 하기 위해서(Promise의 콜백 함수 동작을 단순히 보이기 위해서) asyncawait가 사용됩니다.

var resolveAfter2Seconds = function() {
  console.log("starting slow promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve(20);
      console.log("slow promise is done");
    }, 2000);
  });
};

var resolveAfter1Second = function() {
  console.log("starting fast promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve(10);
      console.log("fast promise is done");
    }, 1000);
  });
};

var sequentialStart = async function() {
  console.log("==SEQUENTIAL START==");

  // If the value of the expression following the await operator is not a Promise, it's converted to a resolved Promise.
  const slow = await resolveAfter2Seconds();

  const fast = await resolveAfter1Second();
  console.log(slow);
  console.log(fast);
}

var concurrentStart = async function() {
  console.log("==CONCURRENT START with await==");
  const slow = resolveAfter2Seconds(); // starts timer immediately
  const fast = resolveAfter1Second();

  console.log(await slow);
  console.log(await fast); // waits for slow to finish, even though fast is already done!
}

var stillSerial = function() {
  console.log("==CONCURRENT START with Promise.all==");
  Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(([slow, fast]) => {
    console.log(slow);
    console.log(fast);
  });
}

var parallel = function() {
  console.log("==PARALLEL with Promise.then==");
  resolveAfter2Seconds().then((message)=>console.log(message)); // in this case could be simply written as console.log(resolveAfter2Seconds());
  resolveAfter1Second().then((message)=>console.log(message));
}

sequentialStart(); // takes 2+1 seconds in total
// wait above to finish
setTimeout(concurrentStart, 4000); // takes 2 seconds in total
// wait again
setTimeout(stillSerial, 7000); // same as before
// wait again
setTimeout(parallel, 10000); // trully parallel

위의 예제는 MDN에 나와 있는 async, await 예제입니다. 코드가 길고 복잡해 보이지만 뜯어 보면 어렵지 않은 코드입니다.

  • resolveAfter2Seconds : 2초후 Promise.resolve로 20을 내보내는 함수입니다.
  • resolveAfter1Second : 1초후 Promise.resolve로 10을 내보내는 함수입니다.
  • sequentialStart : resolveAfter2SecondsresolveAfter1Secondawait를 이용하여 순차적으로 결과를 받는 함수입니다. 순차적으로 결과를 받기 때문에, 2초(resolveAfter2Seconds) + 1초(resolveAfter1Second) = 3초 후 console.log 출력 값을 확인 할 수 있습니다.
  • concurrentStart : resolveAfter2SecondsresolveAfter1Second를 동시(엄밀히 말하면 동시는 아니지만..) 실행하고 그 후 await로 결과를 받는 함수입니다. 동시에 함수들이 실행 되기 때문에 가장 마지막으로 종료 되는 2초(resolveAfter2Seconds) 후 console.log 출력 값을 확인 할 수 있습니다.
  • stillSerial : Promise.all을 사용하여 concurrentStart와 동일한 기능을 구현한 함수입니다.
  • parallel : 병렬로 resolveAfter2SecondsresolveAfter1Second를 실행하는 함수입니다.

참고

댓글
  • 프로필사진 Daniel Hwang 적어 주신 글 유익하게 잘 보고 있습니다. 감사드려요.
    한 가지 여쭈어볼께요.
    병렬(parallel)로 실행된다는 것이 동시 실행된다는 것(concurrent)과 다른 의미로 적으신 것인가요? 아니면 같은 의미인가요?
    2019.04.26 23:20
  • 프로필사진 버미노트 concurrentStart의 concurrent는 await slow로 늦게 끝나는 코드를 먼저 await 하기 때문에, 동시에 로그를 찍는다는 의미이고,

    parallel는 각각의 promise가 따로 동작하여 로그를 찍기 때문에 parallel이라고 표현하였습니다.
    2019.04.30 15:12 신고
댓글쓰기 폼