티스토리 뷰

Promise

한국어로 약속 이라는 뜻이다.

Promise는 자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 객체이다.

Promise는 정해진 기간의 기능을 수행 한 후에 정상적으로 기능을 수행했다면 성공의 메세지와 함께 결과 값을 전달해주고 만약 기능을 수행하는 데에 문제가 생기면 에러를 전달 해준다.

Promise에는 두가지의 포인트가 있다.

  • state : promise의 상태
  • producer와 consumer의 차이

Promise의 상태는 다음과 같이 나뉜다

pending -> fulfilled or rejected

또 promise를 만드는 producer 와 이것을 사용하는 comsumer 로 나뉜다.

콜백 대신 Promise를 사용하는 코드예시를 살펴보자.

const promise = new Promise((resolve, reject) =>{
    // 비동기 작업 수행
    console.log('doing something')
})

Promise는 위와 같이 만들 수 있다.

콘솔을 살펴보면 알겠지만 promise를 만듬과 동시에 해당 작업이 수행되는 것을 볼 수 있다.

따라서 promise를 만들때 불필요한 작업이 바로 수행되지 않도록 유의해야 한다.

resolve

이번엔 비동기 작업을 실제로 수행하는 코드를 짜보자.

const promise = new Promise((resolve, reject) => {
  // 비동기 작업 수행
  setTimeout(() => {
    const data = 'some data'
    resolve(data)
  }, 1000)
})

이 작업은 promise내에서 setTimeout함수를 통해 1초 뒤 data를 전달 해 주는 코드이다.

setTimeout이 정상적으로 수행이 되고나면 callback이 불릴테고, 그때 resolve를 통해 데이터를 전달 해 주는 것이다.

이제 이 promise를 consumer 입장에서 사용 해 보자.

promise.then((val) => console.log(val))

promise가 완료되면 데이터를 받아 콘솔로 찍어주는 코드이다.

콘솔을 확인해보면 정상적으로 1초뒤에 some data 가 출력되는 것을 볼 수 있다.

reject

그렇다면 reject는 언제 사용해야 할까?

reject는 우리가 어떠한 작업을 할 때 작업에 실패하여 에러를 발생해야 할 때 쓰인다.

const promise = new Promise((resolve, reject) => {
  // 비동기 작업 수행
  setTimeout(() => {
    reject(new Error('Error'))
  }, 1000)
})

setTimeout을 수행하던 중 오류가 났다고 가정했다.

reject에는 Error객체를 보낸다. 이 의미는 promise의 작업이 실패했다는 것이다.

이 오류는 아래 코드처럼 catch로 잡아줄 수 있다.

promise
  .then((val) => console.log(val))
  .catch((err) => {
    console.log(err)
  })

위 코드가 가능한 이유는 then역시 promise를 return하게 되고 promise에 있는 catch를 이용한 것이다.

Promise chaining

promise를 통해 얻은 결과를 chaining을 통해 다음 작업에도 계속 쓸 수 있다.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10)
  }, 1000)
})

promise
  .then((value) => value * 2)
  .then((value) => value * 2)
  .then(
    (value) =>
      new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(value / 2)
        }, 1000)
      })
  )
  .then((value) => {
    console.log(value - 1)
  })
  1. 먼저 1초뒤에 10을 받아오는 promise를 만들었다.
  2. 이제 그 promise가 완료되면 결과에 2를 곱하여 promise를 반환한다.
  3. 이어서 또 2를 곱하여 promise를 반환한다.
  4. 여기서 비동기작업이 또 필요하다고 가정하여 1초뒤에 결과에 2를 나누는 promise를 반환한다.
  5. 마지막으로 promise를 받아 1를 뺀 결과를 console하고 연결은 끝이난다.

chaining을 이용한 코드를 한번 보자.

const getEgg = () =>
  new Promise((resolve, reject) => {
    setTimeout(resolve('🥚'), 1000)
  })
const getChick = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(resolve(`${egg} => 🐣`), 1000)
  })
const getChicken = (chick) =>
  new Promise((resolve, reject) => {
    setTimeout(resolve(`${chick} => 🐤`), 1000)
  })

getEgg()
  .then(getChick)
  .then(getChicken)
  .then((chicken) => {
    console.log(chicken)
  })

promise를 이용해 비동기작업을 하여 병아리의 알부터 시작하여 병아리로 부화하는 과정이다.

최종적으로 마지막 then에 도달하면 콘솔에는 🥚 => 🐣 => 🐤 의 결과가 나올것이다.

그런데 만약 중간에 오류가 생겼다면 어떨까?

🥚 => 🐣 이 과정에서 오류를 내보았다. reject(new Error(ERROR! ${egg} => 🐣))

그리고 마지막 then이후에 catch를 통해 오류를 잡아보았다.

getEgg()
  .then(getChick)
  .then(getChicken)
  .then((chicken) => {
    console.log(chicken)
  })
  .catch((err) => console.log(err))

다음과 같은 에러가 나왔다. Error: ERROR! 🥚 => 🐣

그런데 만약에 중간에 에러가 났을 때 그 값을 대체하여 다른 값을 전달해 마지막 함수까지 실행하고 싶을 때는 어떻게 해야 할까?

먼저 reject에게 인자로 받은 data와 error를 전달해 주었다.

reject({ data: egg, error: new Error('error') })

그리고 catch문에서 새로운 값을 return 하여주면 된다.

getEgg()
  .then(getChick)
  .catch(({ data, error }) => {
    console.log(error)
    return `${data} => 🐥 `
  })
  .then(getChicken)
  .then((chicken) => {
    console.log(chicken)
  })
  .catch((err) => console.log(err))

이렇게 하여 코드를 돌려보았을 때 🥚 => 🐥 => 🐤 의 결과를 얻을 수 있었다.


async와 await

promise를 조금 더 간결하고 간편하고 마치 동기적으로 실행되는 것 처럼 보여주는 기능이다.

promise에는 chaining을 할 수 있다. chaining이 길어진다면 아무리 콜백지옥만큼은 아니여도 코드가 난잡해 질 수 있다.

async와 await은 promise위에서 조금 더 간편하게 사용할 수 있는 api이다.

한가지 기억해야 할 점은 무조건 promise대신 async와 await을 사용할 필요는 없다는 점이다.

promise를 유지하여 써야 하는 경우가 있을 수 있다.

async를 붙여 함수를 만들게 되면 우리가 내부적으로 promise를 생성하지 않아도 자동으로 promise를 생성하여 return 해준다.

const promise = new Promise((resolve, reject) => {
  // 비동기 작업 수행
  setTimeout(() => {
    resolve('done')
  }, 1000)
})
promise.then(console.log)

따라서 async를 사용하면 위와 같은 코드를

async function promise() {
  // 시간이 걸리는 작업
  return 'done'
}
const p = promise()
p.then(console.log)

이렇게 바꿔줄 수 있다.

await은 async가 붙은 function 안에서 사용할 수 있다.

then을 사용하는 것과 같은 개념이지만 마치 동기적으로 작동하는 것 같은 코드를 작성할 수 있다.

하지만 한 함수내에서 await을 연달아 사용한다면 병렬처리를 하지 못할 수 있다.

따라서 병렬처리가 필요한 작업에는 별도의 함수를 만들어 병렬처리하고 그 결과를 await을 통해 받아오거나 promise.all이나 promise.race등 promise의 api를 사용하는 것이 좋다.

그럼 위에 promise에서 사용했던 예제를 async와 await을 사용해 바꾸어 보겠다.

const getEgg = () =>
  new Promise((resolve, reject) => {
    setTimeout(resolve('🥚'), 1000)
  })
const getChick = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(resolve(`${egg} => 🐣`), 1000)
  })
const getChicken = (chick) =>
  new Promise((resolve, reject) => {
    setTimeout(resolve(`${chick} => 🐤`), 1000)
  })

getEgg()
  .then(getChick)
  .then(getChicken)
  .then((chicken) => {
    console.log(chicken)
  })

이 코드를

// 1초뒤 결과를 얻는 것을 구현하기 위한 dealy 함수
const delay = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms))

async function getEgg() {
  await delay(1000)
  return '🥚'
}

async function getChick(egg) {
  await delay(1000)
  return `${egg} => 🐣`
}

async function getChicken(chick) {
  await delay(1000)
  return `${chick} => 🐤`
}

async function getResult() {
  try {
    const egg = await getEgg()
    const chick = await getChick(egg)
    const chicken = await getChicken(chick)
    console.log(chicken)
  } catch (err) {
    console.log(err)
  }
}

getResult()

이렇게 마치 동기적으로 동작하는 코드 처럼 변경해 줄 수 있다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함