My Profile Photo

DongChanS's blog


수학과 학생의 개발일지


자바스크립트 (6) - Promise & Async await

1) Promise

1-1. 이상한 객체

우리가 XMLHttpRequest 를 통해서 외부 API로부터 여러 과정을 거쳐서 데이터를 얻을 수 있었는데,

사실 fetch라는 함수를 통해서 쉽게 데이터를 얻을 수 있습니다.

다음은 Mozilla 사이트에서 설명된 내용입니다.

Fetch API를 이용하면 Request나 Response와 같은 HTTP의 파이프라인을 구성하는 요소를 조작하는것이 가능합니다. 또한 fetch() 메서드를 이용하는 것으로 비동기 네트워크 통신을 알기쉽게 기술할 수 있습니다.

이전에 이러한 기능을 XMLHttpRequest에서 제공하고 있었습니다. Fetch는 이러한 API의 대체제로 Service Workers같은 기술로 간단히 이용하는것이 가능합니다. 또한 CORS나 HTTP확장같은 HTTP에 관련한 개념을 모아 정의하고 있습니다.

const URL = "https://jsonplaceholder.typicode.com/posts/"
const res = fetch(URL)
console.log(res)

그런데 실행을 해보면 이상한게 나옵니다.

1

이제 이 이상한 Promise에 대해서 알아보도록 하겠습니다.

1-2. Promise 란?

Promise는 어떤 객체입니다. 설명서를 보면,

Promise(executor: (resolve: (value?: any) => void, reject: (reason?: any) => void) => void): Promise

A callback used to initialize the promise. This callback is passed two arguments: a resolve callback used to resolve the promise with a value or the result of another promise, and a reject callback used to reject the promise with a provided reason or error.

Creates a new Promise.

  • Promise ( executor ) : executor라는 callback을 가짐.
    • executor ( resolve, reject ) : resolve와 reject라는 callback을 가짐.
      • resolve : promise 객체의 결과를 가져오기 위한 callback 함수.
      • reject callback : promise 객체의 에러를 가져오기 위한 callback 함수

어찌됬건 두개의 callback function을 지정해줘야 하니까 이런 식으로 짤 수 있습니다.

const orderCoffee = (order) => new Promise( (resolve, reject) => {
    
    if (order === undefined) {
        reject('주문에 실패함') // 주문실패할때 reject 실행
    } else {        
        setTimeout( () => {
            let coffee = order
            resolve(coffee) // 주문성공할때 resolve 실행
        }, 1000)
    }
})

2

사실 Promise가 리턴되는건 당연합니다. orderCoffee는 order를 인자로 받고 Promise를 반환하는 함수니까요.

그리고, Promise를 잘 보면 “아아” 라는 값을 갖고있음을 알 수 있습니다.

resolve 함수의 인자로 들어가는 coffee 변수의 값을 Promise는 알고 있는 것이죠.

반환된 Promise 객체에서 이 값을 꺼내기 위해서는 then이라는 메서드가 필요합니다.

1-3. then

then의 설명서를 보면,

then(onfulfilled?: (value: any) => any, onrejected?: (reason: any) => PromiseLike): Promise

The callback to execute when the Promise is resolved.

Attaches callbacks for the resolution and/or rejection of the Promise.

@returns — A Promise for the completion of which ever callback is executed.

Promise가 resolve 되었을 때 실행하기 위한 callback fucntion입니다.

그러니까 resolve 함수를 실행하는 것 대신에 then이라는 메서드로 편하게 할 수 있는 것이죠.

예를 들어서,

const resolve = (coffee) => {
    console.log(coffee)
}

이렇게 받은 coffee를 resolve함수를 통해서 출력해주는 로직을 짜고 싶다면,

orderCoffee("아아").then( (coffee) => {
    console.log(coffee)
})

이렇게 로직을 그대로 then에 넣어주기만 하면 됩니다.

3

그러면 then 함수가 작동을 해서 “아아” 가 출력된 것을 알 수 있습니다.

물론 전체적인 함수의 리턴값도 Promise 객체인데, 이 Promise의 [[PromiseValue]]값을 return값으로 관리할 수 있습니다.

4

그래서 이걸 다시 then을 통해서 출력할 수 있습니다.

5

1-4. catch

catch도 then과 비슷하게 reject를 실행하는 함수입니다. 결과값은 매우 비슷합니다.

7

1-5. fetch에 적용해보기.

const URL = 'https://koreanjson.com/posts/1'
const res = fetch(URL)
const res2 = res.then((response) => {
    return response.json()
})
const res3 = res2.then((jsonObj) => {
    console.log(jsonObj)
})

res => Promise : [[PromiseValue]] : response

res2 => Promise : [[PromiseValue]] : Object

res3 => res2에 저장된 Object의 값을 then을 통해서 출력하기.

유의해야할 점은 response.json() 메서드 그 자체도 비동기적으로 작동하기 때문에

const res2 = res.then((response) => {
    console.log(response.json())
    return response.json()
})

이라고 하면, 출력을 위해서 response.json()를 실컷 하고 있는데, 다시 return구문에서 비동기적인 행동을 해서 출력을 하라니까 오류를 출력한다.

2) Async - await

2-1. 직관적으로 코드를 적고 싶다.

const coffee = orderCoffee('따아')
console.log(coffee)

이게 가장 직관적인 방법이기 때문에 이런 방식을 추구하고 싶습니다.

2-2. 해결방법

  1. 우리의 로직을 함수 안에 넣어놓습니다.

    const getCoffee = (order) => {
        const coffee = orderCoffee(order)
        console.log(coffee)
    }
    
  2. async 라는 표현을 통해서

    이 getCoffee라는 함수는 비동기적으로 처리한다는 로직이 내부에 있을 것입니다 라는 선언을 해주는 것입니다.

    const getCoffee = async (order) => {
        const coffee = orderCoffee(order)
        console.log(coffee)
    }
    
  3. 그리고나서 비동기적으로 실행되는 함수 앞에 await를 붙인다.

    await는 이 함수가 실행이 완료될때 까지 기다려 라는 뜻입니다.

    const getCoffee = async (order) => {
        const coffee = await orderCoffee(order)
        console.log(coffee)
    }
    

그렇게 실행을 해보면, 성공적으로 우리가 원하는 결과가 나왔습니다.

async 함수의 결과물은 Promise객체이기 때문에 Promise객체도 같이 출력됩니다.

6

2-3. 요약

const getCoffee = async (order) => {
    const coffee = await orderCoffee(order)
}
const coffee = orderCoffee(order).then( coffee => coffee )

다음 두 coffee 객체는 사실상 똑같습니다.

그러니까 orderCoffee는 Promise 객체를 반환하는데 await 키워드를 쓴다면 then을 체인하는 것보다 더 가독성이 좋기 때문에 이것을 쓴다고 생각하면 될 것 같습니다.

2-4. reject는 어떻게 처리?

에러가 났을 때는 파이썬의 try - except 처럼 try - catch 구문을 통해서 반환할 수 있습니다.

const getCoffee = async (order) => {
    try {    
        const coffee = await orderCoffee(order)
        console.log(coffee)
    } catch (error) {
        console.log(error)
    }
}

이 error는 역시 Promise의 reject에 전달된 인자를 반환합니다.

8

2-5. fetch에 적용해보기

const URL = 'https://koreanjson.com/posts/1'
const getPost = async (URL) => {
    const res = await fetch(URL)
    const res2 = await res.json()
    // res.json도 비동기적인 함수임을 유의하자!
    console.log(res2)
}
getPost(URL)
comments powered by Disqus