티스토리 뷰

async 함수 안에, await 가 한 번 실행되며, 그 실행 결과를 그대로 리턴하는 경우, 굳이 async / await 을 쓸 필요는 없다.

// sample1
async function test1(msg) {
    await wait1SecPrint(msg);
}

위와 같이 어떤 함수에서 test1을 호출하고 await 한다면, test1 내부 코드를 다음과 같이 써도 무방하다는 얘기이다.

//sample2
function test1(msg) {
    return wait1SecPrint(msg);
}

예를 들어 보자면,

//sample3: a.js
async function do_process(msg) {
    console.log("Begin...");
    await test1(msg);
    console.log("Check...");
    await test1(msg);
    console.log("Done...");
}

async function test1(msg) {
    await wait1SecPrint(msg);
}

function wait1SecPrint(msg) {
    return new Promise( function(onResolve, onReject) {
        setTimeout( function() {
            console.log(msg);
            onResolve();
        }, 1000 );
    });
}

async function main() {
    await do_process("Hi there");
}
main();

위의 코드를 실행하면, 

$ node a.js
Begin...
Hi there
Check...
Hi there
Done...

이런 결과가 나온다. 마찬가지로 test1을 sample2의 코드로 바꾸어도 같은 결과가 나온다.

주의할 것은 만약 다음의 sample4와 같이 return이 없이 작성한다면, 원하는 결과를 얻을 수 없다.

//sample4
function test1(msg) {
    wait1SecPrint(msg);
}
$ node a.js
Begin...
Check...
Done...
Hi there
Hi there

함수가 await 를 포함한다면 암묵적으로 Promise 를 return하는 함수라 생각해야한다. sample1의 test1은 return 값이 없는 것이 아니라, Promise를 return하는 함수다.

이것을 babel 을 이용하여 한번 더 확인해 보자.

npm i @babel/cli babel-plugin-async-to-promises

 babel-plugin-async-to-promises 모듈은 오래되긴 했지만, async / await 구문이 어떻게 Promise / then 으로 변환되는지 잘 알 수 있는 babel 플러그인이다. sample3의 a.js를 변환해 보면 다음과 같다. (--no-babelrc 는 .babelrc 가 없다면 생략가능)

$ npx babel --no-babelrc --plugins babel-plugin-async-to-promises a.js

//sample3: a.js
function do_process(msg) {
  return Promise.resolve().then(function () {
    console.log("Begin...");
    return test1(msg);
  }).then(function () {
    console.log("Check...");
    return test1(msg);
  }).then(function () {
    console.log("Done...");
  });
}

function test1(msg) {
  return Promise.resolve().then(function () {
    return wait1SecPrint(msg);
  }).then(function () {});
}

function wait1SecPrint(msg) {
  return new Promise(function (onResolve, onReject) {
    setTimeout(function () {
      console.log(msg);
      onResolve();
    }, 1000);
  });
}

function main() {
  return Promise.resolve().then(function () {
    return do_process("Hi there");
  }).then(function () {});
}

main();

do_process, test1, main 함수들을 보면, 모두 await 를 기준으로 자른 뒤 각각을 함수로 만들고 그 함수를 Promise.resove().then(...) 에 넣어 체인을 만드는 것을 볼 수 있다. 그리고 그 then의 최종 결과를 return 하는 모양으로 변환이 된다. 즉 await 가 포함된 함수는 then()의 결과가 그대로 return되므로 Promise를 return 하는 함수로 취급하는 것이다.

참고로 then은 항상 새로운 Promise 객체를 return 한다. 이 새로운 Promise 객체는 대기 상태이며(Promise의 상태에는 대기와 완료가 있다, 완료에는 다시 성공과 거절 상태로 나뉜다), then은 callback 함수를 두 개 받는데, Promise의 상태가 resolve, reject 으로 바뀔 때 실행될 함수를 등록하는 것이 then 함수의 기본 동작이다. 위 코드는 then에 전달되는 reject callback이 생략된 모습이다.

".then(...)" 체인을 보게 된다면, 두 단계로 해석하는 것이 좋다. 

  1. 정적 해석으로 보자면, then은 Promise 인스턴스의 메소드로서 전달 받은 Promise 인스턴스에 callback 등록하고 새로운 Promise를 return을 계속하는 과정이다. 다음 then은 이 새로운 Promise에 마찬가지로 callback을 등록하는 것이다.
  2. 실행 되는 과정으로 보자면, 한 Promise가 완료될 때 등록된 callback을 호출하며, 이 callback이 return 되면 해당 then이 return 했던 Promise를 완료시킨다. 단 return 으로 넘기는 되는 것이 또 다른 Promise라면, 등록시 return 했던 Promise는 이 Promise가 완료되어야 비로소 완료된다.

만약 do_process 내에서 "return test1(msg)" 이 구문이 return <Promise instance>가 아니라면, return undefined가 되어 해당 then이 먼저 return 했던 Promise는 바로 완료가 된다. 따라서 Begin, Check, Done이 쉼 없이 출력이 된다.

따라서 test1이 wait1SecPrint가 종료된 다음 진행되는 효과를 보이려면, Promise를 return 해야하고, 새로운 Promise를 만들어 보내는 것보다 wait1SecPrint가 전달하는 Promise를 다시 보냄으로써 같은 효과를 얻을 수 있다.

반응형
댓글
  • 프로필사진 system420 안녕하세요 우연히 들어왔다가 마음이 가서 덧글 남깁니다. 썰렁한 엔지니어님 만들고 싶은 system이 있습니다.

    썰렁한 엔지니어님과

    함께 하고 싶습니다.

    혹은 도움받고 싶습니다.

    마음이 움직이신다면 연락 부탁드리겠습니다.

    감사합니다.

    system420a@gmail.com 입니다.
    😁
    2022.08.06 19:21 신고
댓글쓰기 폼