Contents

Async To Sync in Javascript

Async to Sync in JavaScript

누구나 웹개발을 하다보면 HTTP 통신을 통해 데이터를 가져오고 가공하고 사용하게됩니다. 그리고 동기, 비동기 처리 때문에 골치가 아파집니다. (만약 골치가 아프지 않다면.. 부럽습니다..) 더 이상 골치아프기 싫어서 정리해봅시다.👍

*동기, 비동기 설명은 이전 포스팅으로 갈음합니다.

Async(비동기) to Sync(동기) 방법

자바스크립트에서의 비동기를 동기로 동작시키는 대표적인 3가지의 방법을 알아보겠습니다.

  1. Callback

  2. Promise

  3. Async / Await

1. Callback

개발자들 마다 정의가 조금씩 다르겠지만, 저는 어떤 이벤트가 발생한 후, 수행될 함수로 정의해보겠습니다.

  • 예제를 보며 이해해 봅시다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // Callback 사용 예제
    // a와 b라는 함수를 선언
    
    function a(callback) {       // 함수 a는 callback이라는 함수를 받는다.
      const data = 'i am data'   // i am data라는 값을 담은 변수 data를 생성
      callback(data)             // callback이라는 함수에 data라는 변수를 넘겨줌.
    }
    
    function b(value) {			  // 함수 b는 입력받은 값 value를 콘솔에 출력하는 함수.
      console.log('넘겨받은 값', value)
    }
    
    // 사용해 봅시다.
    a(function(value) {                   // 함수 a에 function을 받는데 값으로 value를 받는다. 
      console.log('넘겨받은 값', value)     // 함수 a가 실행되면 자동으로 const data를 만드는데 data에는 'i am data'라는 값이 할당. 되고 value가 된다.
    })
    
    // 위를 간소화한 코드.
    a(b)
    

    위의 예제 코드가 눈에 들어왔다면, 먼저 비동기적 코드를 만들어 봅시다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // Callback 비동기적 코딩
    // a가 동작하고 b가 동작하길 원하며 코딩
    
    // a와 b라는 함수를 선언
    function a(callback) {       // 함수 a는 2초 후에 동작
      setTimeout(function() {
          console.log('running a....')
      }, 2000)
    }
    
    function b() {       // 함수 b는 1초 후에 동작
      setTimeout(function() {
          console.log('running b....')
      }, 1000)
    }
    
    // 실행해봅시다.
    a()
    b()
    

    setTimeout이라는 javascript의 가장 대표적인 동작 지연 함수를 사용해 코딩해보았고

    결과로 a → b의 순서가 아닌 함수 동작 시간에 따른 딜레이를 확인해보았습니다.

    어떻게 해야 내가 코딩한 순서대로 (a → b) 동기적으로 작동하게 만들 수 있을까요?

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // Callback 동기적 코딩
    // a가 동작하고 b가 동작하길 원하며 코딩
    
    // a와 b라는 함수를 선언
    function a(callback) {       // 함수 a는 2초 후에 동작
      setTimeout(function() {
          console.log('running a....')
          callback()			 // 👈 callback함수를 추가
      }, 2000)
    }
    
    function b() {       // 함수 b는 1초 후에 동작
      setTimeout(function() {
          console.log('running b....')
      }, 1000)
    }
    
    // 실행해봅시다.
    a(b)
    

    원하는 동작 console.log(‘runing a….')뒤에 callback함수를 넣어 console.log(‘runing b….')를 동작했습니다.

    간단해보이지만 강력하고, 막상 적용하려면 어려울 수 있습니다. 연습을 추천합니다.😊

    근데 callback 함수는 대부분의 개발자들이 간단한 코딩에만 사용합니다. 단순히 1, 2개 정도의 요청이라면, callback 함수로 처리할 수 있지만, 만약 그 수가 많아진다면, 소위 callback HELL이 되기 때문입니다.

    그래서 다른 방법도 알아봅시다!

2. Promise

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다. 간단히 비동기 작업에 사용되는 객체입니다.

  • Promise는  ex) new Promise ( function ( resolve, reject )) 의 형태로 사용됩니다.

    특징으로 Promise의 인자는 반드시 function을 받아야 합니다. (동작 성공시 실행될 resolve라는 함수와 실패시 동작할 reject / 2개의 인자를 받습니다.)

  • Promise 는 3가지 상태가 존재합니다.

  • 만약 인자를 받지 않거나 함수 이외의 값을 전달하면, Uncaught TypeError가 발생하고, 인자로 전달된 resolve 와 reject 중 하나라도 호출하지 않는다면, 영원히 pending 상태로 대기하게 됩니다.

    또 Promise 객체들은 .then() 을 통해 이어질 수 있고 .catch() 를 통해 에러 캐치를 할 수 있습니다. 이 부분을 활용해 동기 처리를 합니다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    // Promise 사용 예제
    
    function test() {                                  // test라는 동작(함수)을 만드는데
      return new Promise(function(resolve, reject) {   // Promise로 만들고 Promise는 인자로 resolve, reject를 받는다.
          try {                                        // 정상처리되면(조건은 다양하게 구성)
              resolve(                                 // resolve를 동작하는데
                  console.log('Promise resolve')       // Promise resolve를 출력하고
              )
          } catch {                                    // error가 발생하면
              reject(                                  // reject를 동작하는데
                  console.log('Promise reject')        // Promise reject를 출력한다.
              )
          }
      })
    }
    
    test()                            // test를 동작(함수실행)하는데
    .then((res) =>                    // 에러가 없다면
      console.log('정상 처리 완료')    // '정상 처리 완료'를 출력하고
    )
    .catch((err) =>                   // 에러가 발생했다면
      console.log('에러 발생')        // '에러 발생'을 출력한다.
    )
    

    위의 예제 코드가 눈에 들어왔다면, 이제 동기적 코드를 보며 이해해봅시다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    // Promise 동기적 코딩
    // 1,2,3 순서대로 출력되길 원하며 코딩
    
    function test() {                                    // test라는 동작(함수)을 만드는데
      return new Promise(function(resolve, reject) {   // Promise로 만들고 Promise는 인자로 reselve, reject를 받는다.
          try {
              setTimeout(function() {					// AJAX등의 처리를 기다리는 '지연 시간'을 setTimeout으로 설정
                  resolve( console.log('1') )			// resolve처리를 하는데 3초의 딜레이를 주고
              },3000)
              console.log('2')                    	// resolve 
          } catch {
              reject(
                  console.log('reject running')
              )
          }
      })
    }
    function b() {
      setTimeout(function() {
          console.log('3')
      }, 1000)
    }
    
    // 실행해봅시다.
    test()
    .then(b)
    // .then(function() {b()})
    
    //////////////////////////////////////////////////////////////////////////////////////
    // .then(b) === .then(function() {b()}) === .then(() => {b()}) !== .then(b())
    // .then(b) / .then(function() {b()}) / .then(() => {b()})는 
    // "test함수에 오류가 없다면 b함수를 return 하라" 라는 의미기 때문에 동기적인 처리가 가능!
    // 반면 .then(b())는 "test함수에 오류가 없으면 b라는 함수를 실행하라" 라는 의미기 때문에
    // 순서에 상관없이 오류가 없다면 b를 즉시 실행한다.
    // 비슷하게...
    // .then(console.log('hi'))는 "요청들어오면 hi를 출력하라" 라는 의미고
    // .then(function() {console.log('hi')}) or .then(() => {console.log('hi')})는 
    // "어떤 요청의 결과로 hi를 return하라" 하는 의미기 때문에 동기적으로 동작한다.
    

    위 설명에는 1,2,3 순서대로 출력되길 원하며 코딩이라고 적어놨지만, 결과 화면은 다른 것을 알 수 있습니다. 실은 2,1,3 순서대로 출력되는 것을 의도한 코딩입니다.

    2의 경우 resolve에 포함되어 있지 않기 때문에 *인터프리터 언어 특징에 따라 진행되기 때문입니다.

    *인터프리터 언어의 특징은 다른 포스팅에 정리되어 있습니다.

    하지만 Promise 또한 단계?가 많아진다면 가독성 매우 떨어질 수 있습니다. 사실 callback과 Promise를 사용하는데 가독성에 문제가 있다면, 본인의 코드를 의심해봐야 될 수 있습니다.👀ㅎ.

3. Async / Await

Async(비동기) / Await(기다려!)는 비동기 처리를 하는 방법 중 하나입니다. Promise 나 Callback 보다 가독성이 좋은 코드를 짤 수 있다고 생각합니다.

  • 위에 Promise를 살펴본 이유가 있습니다. Async/await의 기반이 Promise이기 때문입니다. 우리가 쓰는 모든 async 함수는 Promise를 리턴하고, 모든 await 함수는 일반적으로 Promise가 됩니다.

  • Async/await는  ex) async function functionName()  { await  promiseFunction } 의 형태로 사용됩니다.

  • sync function은 내부에 await 문법을 사용하기 위해 선언합니다.

  • async function은 AsyncFunction 객체를 반환하며 ‘async function’ 표현식으로만 생성됩니다.

  • async function은 인자를 받을 수 있으며, 실행 후 반환 값은 Promise 입니다.

  • await 연산자는 async function 내부에서만 사용할 수 있습니다. (외부에서 사용하면 SyntaxError가 발생.)

  • await는 반환된 Promise의 resolve의 상태까지 대기하며, await의 값은 Promise의 resolve값이 반환됩니다.

  • 만약 중간에 reject를 만나면 해당 async function스코프의 작업을 중단되며, 이후 스코프 작업이 진행됩니다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    // Async/Await 사용 예제
    
    function promise() {		
      return new Promise(function(resolve, reject) {
          resolve(10)
      })
    }
    
    async function foo() {
      const num = await promise()
      console.log(num)
    }
    // 돌려봅시다!
    foo()		// 10
    

    위의 코드가 이해 되셨다면 동기적 사용에 대한 코드를 보며 이해해 봅시다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    // Async/Await 동기적 코딩
    
    function promise() {
      console.log('AsyncAwait running...')
      return new Promise(function(resolve, reject) {
          setTimeout(function() {
              resolve(10)
          }, 5000)
      })
    }
    
    async function foo() {
      console.log('foo running...')
      const num = await promise()
      console.log('num:', num)
    }
    // 돌려봅시다!
    foo()
    

    가독성이 훨씬 뛰어나다고 생각합니다.

    Promise를 선언하고 이를 await 걸어, 순서를 정리하는 방법으로 코딩을 하다보니 가독성은 좋지만, Promise의 사용법도 익혀야 한다는 단점이 있습니다.


👀요약

공부한 3가지 방법 모두 유용하고 강력한 기능이라는 생각입니다. 모두를 익히면 좋고 선택적으로 익혀야 한다면 본인에게 잘 맞는 방법으로 익혀 사용하면 좋을 것 같습니다.

🤷‍♂️삽질

  • Async/Await 관련해서 혼자 이런 저런 테스트를 해보니 위에 설명한 것과 같이 new Promise()로 선언한 것들만 동기적으로 작동했고 일반 function으로 선언한 것들은 원하는 대로 동작하지 않았습니다.

    또 주의할 점에

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    // 여러 test 중 하나. 
    
    function promise() {
      console.log('AsyncAwait running...')
      return new Promise(function(resolve, reject) {
          setTimeout(function() {
              resolve(10)
          }, 5000)
      })
    }
    
    function test2Promise() {
      console.log('test2Promise 시작')
      return new Promise(function(resolve, reject) {
          setTimeout(()=> {
              console.log('test2Promise running...')
                                      // 👈 Promise의 resolve가 없습니다.
          },3000)
      }) 
    }
    
    async function foo() {
      console.log('foo running...')
      const num = await promise()
      // await test1()
      await test2Promise()
      console.log('num:', num)
    }
    // 돌려봅시다!
    foo()
    

    test2Promise의 resolve를 등록하지 않으면 이전 Promise()인 promise()도 resolve가 정상적으로 진행되 않는 것을 볼 수 있습니다.

    위에서 설명한

    만약 인자를 받지 않거나 함수 이외의 값을 전달하면, Uncaught TypeError가 발생하고, 인자로 전달된 resolve 와 reject 중 하나라도 호출하지 않는다면, 영원히 pending 상태로 대기하게 됩니다.

    가 발생한 것입니다.

  • 공부를 하던 중 .then()에 대한 흥미로운 내용을 확인했습니다.

    .then()은 요청의 결과가 성공했을 때 사용하는 것으로만 생각했는데 인자를 2개 받는 다는 것을 알았습니다. 확인해보니 .then() 에 넘겨지는 인자는 선택적(optional)이었습니다. 그리고

    catch(failureCallback)then(null, failureCallback) 의 축약었습니다.

참고 했던 글

Special Thanks to kmpak.sfy✨.