Promise 패턴으로 계획하고 Promise한테 감시받기

률루랄라·2020년 6월 4일
0
post-thumbnail

아래 글에서 자바스크립트 코드가 어떻게 실행되는지 아주 살짝 맛만봤다.
그 맛을 보니 비동기함수를 처리하는 맛도 봤는데 그 때 발생한 이슈에 대처해서 사용할
Promise에 대해 알아보자.
https://velog.io/@_jouz_ryul/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%99%80-%EC%97%94%EC%A7%84-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%8B%A4%ED%96%89-%EB%B0%A9%EC%8B%9D

개발을 하다보면 API request를 통한 서버와의 교신으로 데이터를 받아와서 사용하는 경우가 많다. 처음에는 이 API request나 Web API를 사용하여 데이터를 받아 사용해야 되는 경우 많은 에러와 어려움을 마주쳤는데 이 모든게 자바스크립트가 비동기 함수를 조금 특이하게 처리하기 때문이다.

이에 대한 해답은 Promise 패턴을 사용하는 것인데 Promise는 하나의 약속과도 같은 개념이다.
그럼 그 약속은 무엇이며 ECMA Scirpt 6 스펙에 정식 포함되기까지 했는지 알아보자.


1. 비동기 함수 처리로 인한 이슈.

자바스크립트는 기본적으로 동기적으로 코드가 실행된다. 하지만 비동기 함수에 대해서는 비동기적으로 처리하는 조금 특이한 점해서 이슈가 발생한다.
대표적인 비동기 함수인 fetch함수로 실제로 통신을 하는 코드를 예제 삼아 알아보자.


1.1. 비동기 함수 이슈.

먼저 어떤 이슈가 발생하는지 알아보자.

let example = 1

let fakeFetch =() => {
  setTimeout( ()=> {
    return example =5
  },3000)
}
fakeFetch()
console.log(example)
// expect example value : 5
// actual example value: 1

우선 fakeFetch함수라는 서버와의 통신을 하는데 3초가 걸린다는 가정으로 만든 가짜 fetch함수, 즉 비동기 함수다.
이 비동기 함수로 서버와의 통신으로 5 라는 값을 가져와
example변수에 다시 할당하는 코드를 작성했다.
그리고 함수를 실행하고 example값이 어떻게 바뀌는지 보자.
여전히 1 이다.
그 이유는 알다시피 비동기 함수의 실행 순서가 제일 뒤로 밀렸기 때문에 5라는 값을 할당하기 전에
console.log(example)이 실행되어 변경될 값이 아닌 현재의 값 1을 사용한 것이기 때문이다.

이렇게 데이터를 실제로 서버로부터 받아서 사용할 때 시점의 문제가 생긴다.
서버로 받은 데이터를 받아서 사용할 코드의 실행 순서는
비동기 함수를 사용해서 데이터를 받아오기 때문에 항상 먼저일 수 밖에 없는 문제가 생긴다.
그럼 어떻게 해결할까?
받아온 데이터를 사용할 코드도 비동기로 사용해야하나?
그렇지 않다. Promise를 사용하면 된다.


2. Promise는 어머니다: Promise 내부 파헤치기

Promise는 고유의 패턴을 이용해 가정 및 계획표를 세울 수 있다.
패턴이 계획표를 세우는 것이라면 Promise는 Promise로 계획한 계획표가 지켜지는지 감시를 해준다고 (사실은 검사가 맞다) 생각하면 된다.
예를 들어 방학 생활 계획표를 Promise 패턴으로 짰다면 Promise가 어머니의 역할로서 잘 지키는지 감시하고 도와준다.
계획대로 지키지 못하면 지키지 못했다고 잔소리를 (사실상 에러를 catch해주는 아주 좋은 기능) 덧붙혀 준다.

말로하니 어렵다. 역시나 예제 코드와 함께
Promise가 무엇인지
Promise 구조는 어떠한지
Promise 패턴은 무엇인지
Promise가 어떻게 감시를 하는지
하나하나 알아보자.

(Promise와 Promise패턴을 구분해서 정리하면 이해하기 쉽다.
Promise 패턴은 Promise를 사용하기 위한 코드 구조라고 생각하면 쉽다.
)


//Promise 사용과 Promise 패턴
let someVal = 1
let API_EXAMPLE_PROMISE =() => {
  1️⃣return new Promise(
  2️⃣(resolve,reject)=> {
    setTimeout(()=> {
      if (someVal===1) {
 resolve(someVal + 10)
      } else {reject(console.log("Fail"))}
},3000)
  })3️⃣.then(data=> console.log(data))
}
API_EXAMPLE_PROMISE()
// console: 11

1️⃣ Promise 선언.
2️⃣ resolve는 성공, 즉 조건에 성공했을 때 그리고 reject는 실패, 즉 조건에 실패했을 때 사용하는것.promise 안에서 꺼내 쓰는것이다. 다시 말해 조건에 성공했다면 resolve()안의 인수값을
리턴한다고 일단 생각하자.

3️⃣ Promise chain 즉 패턴의 형식이다. .then을 사용하여
Promise안에 콜백으로 작성한 코드의 응답이 들어오면 이라고 가정하는 거라고 볼 수 있다.
promise 패턴으로 api 호출 등으로 받아오는 값을 처리할 수 있다.
이 예제는 resolve의 예제이기 때문에 resolved가 되었고
.then으로 리턴된 값 11을 콘솔에 사용할 수 있었던 것이다.
(패턴에 대해서는 밑에 다시 정리할 예정)

위 예제는 Promise 패턴으로 3초 뒤에 실행될 함수가 연산할 값을 사용할 수 있는 형태에 대한 예제이고 가장 기본적인 형태의 Promise 사용법이다.
지금 한줄 한줄 읽어봐도 좋지만 먼저 Promise 내부를 하나하나 살펴보고 그 다음 추가로 다시 예제와 함께 알아보자.

2.1. Promise 상태

위의 코드 형태를 살펴보면 우리가 3초뒤에 연산의 값을 실행하는, API 요청 (response값을 받아오는데까지 시간이 걸리는)에 관한 함수를 Promise의 인수로 넣어줬다.
그 후 if 문으로 조건을 걸어 조건에 일치하면 연산을 실행하고 조건과 다르면 fail이라는 문자열을 콘솔로 보여주라고 코드를 작성했다. 이게 무슨 뜻일까?
new Promise()라고 Promise를 선언하고 그 안에 우리가 코드를 작성하면
해당 조건들에 따라 Promise는 상태를 가진다. 먼저 Promise의 상태들 중 3개에 대해서 알아보자.

// pending state
let sampleVal = 1
let pendingState = () => {
  return new Promise(()=> {
    return sampleVal
  })
}
console.log(pendingState())

// fulfilled or resolvesd state
let sampleVal = 1
let pendingState = () => {
  return new Promise((resolve)=> {
     resolve(sampleVal)
  })
}
console.log(pendingState())
// rejected
let sampleVal = 1
let pendingState = () => {
  return new Promise((resolve, reject)=> {

     reject(sampleVal)
    
  })
}
console.log(pendingState())

위 코드를 한 케이스당 하나씩 콘솔에 찍어보자. 아마 다음처럼 나올 것이다.

pendingfulfilled or resolvesd state
rejected

이 세개가 대표적인 Promise의 상태이다.
1) pending: 기본 Promise의 상태로서 수행하기 전의 상태이다. 즉 default 값인 것이다.
2) resolved(fulfilled): 약속이 지켜진, 즉 계획대로 된 상태이다.
3) rejected: 약속이 어겨진, 즉 계획대로 된 것이 아닌 상태이다.

이렇게 Promise는 Promise 패턴에 대해서 지켜지는지 지켜지지 않았는지 일일히 확인해준다.
그리곤 알려준다. 계획대로 잘 됐는지(resolved) 아니면 계획대로 되지 않았는지.

3. Promise 패턴은 계획표다.

그럼 대체 Promise 패턴 무엇일까?
말 그대로 약속이다. 다른말로 하면 가정이라고 볼 수 있다.
위에서 말한 것처럼 자바스크립트 엔진은 특정 함수들의 호출에 대한 실행을 뒤로 미룬다.
언제까지? 다른 일반 함수들의 실행이 모두 끝날 때 까지.


한마디로 Promise 패턴은 계획표이자 가정이다.
초등학교 때 방학이 되면 방학 계획표를 짰던 기억이 난다.
주로 다음과 같은 계획표를 짰다.
1. 아침 9시에 일어난다.
2. 일어나서 아침을 먹는다.
3. 아침을 먹고 수학을 공부한다.
4. 수학 공부를 하고 점심을 먹는다
...
이런식으로 (물론 지키지 못했지만) 가정을 기반으로 계획을 짰었다.
이게 프로미스다. 무슨말일까?
다시 위의 예제 코드를 가지고 알아보자.

// API 호출 예제
let someVal = 1
let API_EXAMPLE = ()=> {
setTimeout(function() {
 2️⃣someVal + 10
},3000)
3️⃣return someVal
}

4️⃣let someFinalVal = 1️⃣API_EXAMPLE() + 100
console.log(someFinalVal)
//expected someFinalVal = 111
// actual someFinalVal = 101

1️⃣의 실행 결과값으로 2️⃣에서 원하는 연산을 하고 3️⃣에서 결과값으로 반환하여 4️⃣의 변수에 할당될 값의 연산값으로 사용하고 싶었는데
2️⃣의 연산값이 적용되지 않았다. 실제로는 1️⃣의 실행 결과는 11이 되게 하고 싶었지만 전역에 선언된 1을 가져와 4️⃣변수의 값은 101로 할당 된 것이다.

그런 결과는 2️⃣의 연산을 실행할 함수가 비동기 함수여서 자바스트립트가 실행을 다른 곳에 위임했고
콜 스택에서 쌓인 다음 연산을 실행했기 때문에 반영되지 못한것이다.

그럼 여기에 Promise 패턴을 사용하면 어떤 가정을, 어떤 계획표를 세울 수 있는 것일까?
아마 콜 스텍에 쌓이지 않은 2️⃣의 연산을 할 함수의 실행을 가정할 수 있을것 같다.
Promise 패턴을 사용하여 가정을, 계획표를 세우면 다음과 같이 해석할 수 있다.
"이거 값을 할당하는 response까지 3초가 걸리는데 그냥 값이 할당되었다고 가정하자."
혹은
"이거 값이 들어올건데 그 값이 들어오면 2️⃣번 연산을 실행 할거야."
이런식으로 가정을 하거나 계획을 세우면 Promise 패턴을 사용하는 것이다.

3.1. 실제로 가정해보기

그럼 실제 fetch함수를 사용해서 알아보자.

// 실제 받아올 데이터
//https://jsonplaceholder.typicode.com/users
[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
  ...
  ]

위 주소로 접속하면 실제 열개의 객체가 담긴 배열이 나온다.
이 주소로 실제 서버 통신으로 우리가 값을 받아볼 수 있는데 그럼 코드를 작성해보자

let myData =""
let getCollectionData = () => {
  myData = fetch('https://jsonplaceholder.typicode.com/users').then(response=> response.json()).then(response=> response[0])
  return myData
}
getCollectionData()

console.log(myData)

fetch함수를 사용해 서버와의 통신으로 데이터를 받아와서 0번째 인덱스의 객체를 콘솔에 찍어보는 코드다.
그냥 Promise 패턴은 .then이라고 알아두고 (뒤에서 다시 정리할 예정)
콘솔을 확인해보자!

Promise {<pending>}이라는 녀석만 보일 것이다.
Promise를 사용해서 문제를 해결하려고 했는데
결과가 그냥 Promise가 찍힌다.
비동기로 처리되어 연산값을 사용할 수 없는 이슈는 해결된 것 같은데 Promise의 사용도 아니고 Promise 패턴만 사용했는데
Promise가 결과로 나오다니 무슨말인가?

잘 살펴보면 위의 Promise 예제와 그전의 Promise를 선언하여 사용한 예제와 다른점이있다.
이 예제에는 Promise 선언부가 없다.
재밌는점은 fetch는 Promise를 반환한다.
그래서 .then, Promise 패턴을 사용할 수 있었던 것이다.
명심할 것은 Promise에만 .then이 적용된다.
그럼 Promise가 리턴되는건 무슨 말일까?

4. Promise는 Promise를 반환한다?

The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.
출처: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

MDN 공식문서에 나온 내용이다. fetch함수의 실행은 Promise를 반환한다. 하지만 요청 에러 상태에 대해서 reject하지 않고 정상적으로 resolve상태로 반환되며 네트워크 연결 실패시에만 reject한다고 나와있다.
여기서 의문점
1) Promise를 반환한다는것이 무슨뜻일까?
2) 왜 reject 하지 않을까?

그럼 Promise가 Promise를 반환한다는 것이 무슨 뜻이며 왜 그러한것일까?
다시 Promise 사용법을 상기시켜보자.

  1. new Promise로 Promise임을 선언해준다.
    즉 자바스크립트 내장 함수로 저장된 Promise 객체를 new 키워드를 통해인스턴스화 한것이다.
  2. Promise의 인수로 실행하고자 하는 함수를 작성한다.

그럼 이제 Promise 상태와 잘 연결해서 생각해보자.

  1. new Promsie로 Promise를 선언하여 인스턴스화 한다.
  2. Promise의 인수로 무명 함수를 넣어 우리가 얻고자 하는 실행을 작성한다.
  3. 그 결과 값이(resolve 혹은 rejected) Promise로 return되어 Promise를 인스턴스화한 함수의 반환 된다.

역시나 예제를 통해 쉽게 알아보자.

순서대로 pending-resolved-rejected가 콘솔에 찍힌다.
Promise의 대표적 상태 3가지를 다시 적어보면
1. pending
2. resolved
3. rejected
인데

1번 사진은 Promise 안에 작성한 함수의 return으로 아무것도 하지않고 값만 반환하였다.
즉 resolve 나 reject로 Promise의 상태를 변화시킬 조건이나 값이 하나도 없는것이다.
그래서 Promise는 아무것도 하지않고 태초의 default인 pending으로 유지되어 현재의 상태를 return 한것이다.

2번과 3번은 resolve 와 reject로 Promise 상태를 바꿀 값 혹은 조건을 넣어줬다.
그랬더니 Promise의 상태가 바뀌어 콘솔에 해당하는 상태가 찍힌것이다.

이렇게 Promise를 사용하면 resolve, reject로 Promise의 상태를 바꿔줘야 한다.
그 이유는 Promise 패턴으로 비동기 함수 실행에 대한 계획표를 짠다고 했는데
Promise의 상태에 따라 우리가 실행하고 싶은 일들을 계획할 수 있기 때문이다.

resolve를 예로 들어, resolve의 조건으로 sampleVal을 넣는다면
이로 인해 Promise 상태가 resolved인 콜백함수가 반환 될 것이고 .then으로 그 콜백함수에 접근할 수 있어 매개변수로서 받아서 사용이 가능해지기 떄문이다.

반대로 reject의 조건으로 sampleVal을 넣어 이에 해당할 때 실행할 일을 .then으로 계획한다고 코드를 짠다면 그 후의 계획은 CALL BACK 함수로써 매개변수로 sampleVal을 사용할 수 있게 되기 때문이다.

정리하자면 Promise를 인스턴스화 해서 사용하면 Promise를 반환하는데 resolve와 reject의 조건에 따른 반환값이나 할당된 값에 각각 대응하여 Promise의 상태를 resolved나 rejected로 바꿔서 콜백함수를 반환한다.
(resolve와 reject의 값이 없거나 조건이 없으면 pending 유지)
이에 Promise 패턴을 사용할 때 .then으로 미래의 일을 계획하며 작성을 하는데
이때 Promise의 변한 상태에 따른 각각 실행하고 싶은 코드를 작성할 것이다.
이떄 이들은 call back함수로서 반환한 Promise의 값을 매개변수로 사용할 수 있게된다. 그렇기에 Promise의 상태를 각 조건과 값에 따라 바꿔서 반환해 주는 것이다.

아마 눈치챘겠지만 그렇다. Promise 패턴은 결국 callback을 사용하는 것이다.
그럼 이미 callback으로 해결이 가능한 실행 처리를 왜 굳이 Promise 패턴을 사용하는 것일까?

5. Promise에게 감시받는 방법.

간단하게 말하면 에러 핸들링이 편한다. 왜냐? 어머니의 감시처럼
Promise가 철저하게 에러에 대해서 지켜봐주고 있기 때문이다.

// Promise 패턴을 사용하지 않고 일을 계획하는 것

let sampleVal = 1
let callbackFunc =     (sampleVal)=> {
       return sampleVal+1
     }

let pendingState = (callbackFunc) => {
  return callbackFunc(callbackFunc(callbackFunc(callbackFunc(sampleVal))))
}
console.log(pendingState(callbackFunc))

// Promise 패턴을 사용하여 일을 계획 하는 것
let sampleVal = 1
let callbackFunc =     (sampleVal)=> {
       return sampleVal+1
     }
let pendingState = (callbackFunc) => {
  return new Promise((resolve, reject)=> {

    resolve(callbackFunc(callbackFunc(callbackFunc(callbackFunc(sampleVal)))))

    
    
  })
}
console.log(pendingState(callbackFunc))

물론 이번 예제는 Promise의 감시능력에 대해 보기 위해 똑같이 callback으로 실행을 했다.
밑의 코드에서 아무 오타나 한번 내보자.
한번 첫 callbackFunc에 들어가는 sampleValsampleVla로 바꾸고 콘솔창을 지켜보자.
Promise에서 오류가 발생하고 그 내용도 자세히 알려준다.
더 나아가 Promise 패턴은 가독성이 좋다. 지금 위의 코드들은 도대체 뭘 하고있는지 추적하기가 쉽지 않다. Promise 패턴의 .then 매소드를 사용해보면 가독성이 훨신 좋아진다.

let sampleVal = 1

let pendingState = () => {
  return new Promise((resolve, reject)=> {

    resolve(sampleVal)

    
    
  }).then((val)=> {return val+1}).then((addedVal)=> {return addedVal *10}).then(multipliedVal=> {return multipliedVal + 10})

}
console.log(pendingState())

가독성이 훨신 좋아졌다. 어떤 함수가 무엇을 받아서 뭘 하는지 차례차례 잘 보인다.
콘솔창은 다음과 같을 것이다.

그런데 뭔가가 눈에 거슬린다.
아까는 분명 resolved였는데 .then메소드를 사용해서 계획하고 실제로 실행했는데도 Promise의 상태는 resolved인데 왜 console에는 pending이라고 찍히는 걸까?

6. 돌아온 싱글 settled

그 이유는 Promise가 fulfilled (resolved) 또는 rejected의 상태로 바뀌면 앞선 예제처럼 .then메소드로 다음 실행을 handle 할 수 있게 된다.
(그래서 .then이 handler라고도 불린다.)
그런데 Promise가 제공하는 .then/catch/finally등의 여러 핸드러는
Promise가 pending 상태 일때 호출을 기다린다.
그리고 상태가 fulfilled 나 rejected가 되면 Promise의 상태는 바로 settled라는 상태로 바뀐다.
이는 한번 fulfilled나 rejected가 되었던 Promise의 pending 상태를 일컷는 말이다.

이 settled 라는 상태가 되면 .then/catch/finally등의 Promise가 제공하는 여러 핸드러가 즉시 실행이 가능하다.
그 후 핸드러들의 실행이 반환되어 실행이 끝나면 Promise의 상태는 다시 pending이 되는데 처음 Promise를 인스턴스했을 떄의 pending이 아닌
한번 rejected 나 fulfilled로 상태가 바뀐 대기 상태를 말하는 settled상태 인것이다.
쉽게말해 돌싱이라는 이야기다.
그래서 콘솔에 pending이라고 찍히는 것이다.

정리하면 Promise의 실체는 resolve 나 reject를 통해 Promise의 상태를 바꿔준 후
각 상황에 따라 Promise의 상태를 fulfilled(resolved)나 rejected로 바꿔준다.
이렇게 상태가 바뀐 Promise는 즉각 pending 상태로 바뀌는데
이 떄의 pending은 돌싱이다. 즉 한번 상태가 바뀌었다, 즉 사용되었음을 인지하고 있기 때문에
.then 등 Promise가 제공하는 메소드 혹은 핸들러를 즉각 실행을 가능하게 해준다.

그럼 이제 내부를 까봤으니 생각을 머리속에 잘 조립하여 앞서 시도하던 실제 fetch 함수를 Promise 패턴으로 실행해보자.

7. API 호출 실전

자 다시 3.1의 예제코드를 실행해보자.

이제 원리를 알았으니 적용해서 사용해보는 일만 남았다.

// 실제 받아올 데이터
//https://jsonplaceholder.typicode.com/users
[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
  ...
  ]
  let myData =""
let getCollectionData = () => {
  myData = fetch('https://jsonplaceholder.typicode.com/users')
  return myData
}
getCollectionData()

console.log(myData)

콘솔을 확인하면 잘 찍히는 것을 알 수 있다.
이게 바로 Promise를 인스턴스화 해서 사용하고 Promise 패턴으로 가독성을 높히고 에러를 잡아내는 방법이다.

  1. 총 정리 하면 자바스크립트 엔진이 자바스크립트 코드를 비동기 실행 방식으로 실행하는데,
    함수에 있어서, 조금 특이한 방법으로 실행한다.

  2. 대표적으로 API 요청 같은 경우에는 실행의 순서가 맨 뒤로 옮겨지기 때문에 코드들을 순서대로 작성하고 API 요청으로 변수에 값을 담으려 해도 결국 값은 담기지 않는다.

  3. 그래서 Promise를 사용하고 Promise가 제공하는 핸드러를 사용하여 계획표를 짠다.

  4. 이 곳에서는 callback으로써 값을 받아서 사용하기 때문에 에러가 발생하지 않을뿐더러
    Promise 패턴을 사용하면 가독성이 좋아져서 디버깅도 쉽고 에러를 찾아내는 것도 수월해진다.

  • 다음 블로그에 Promise의 자주 쓰이는 핸들러인 try/ catch/ async/ await를 정리하도록 하고
    기본 Promise 정리를 끝내도록 하겠다.
profile
💻 소프트웨어 엔지니어를 꿈꾸는 개발 신생아👶

0개의 댓글