Front/Javascript

비동기 처리 - javascript 실전

oodada 2024. 2. 29. 17:02
반응형

비동기 처리

비동기 처리란, 작업을 동시에 처리하는 것을 말한다.

자바스크립트는 싱글 스레드 언어이기 때문에, 한 번에 하나의 작업만 처리할 수 있다.
하지만, 비동기 처리를 통해 여러 작업을 동시에 처리할 수 있다.

동기와 비동기

  • 동기처리의 예:
    은행에서 번호 순서대로 업무를 처리하는 것, 순차적으로 처리되는 것

  • 비동기처리의 예:
    커피 주문 시 앞번호가 먼저 나오는 것, 동시에 처리되는 것

- 동기 처리

동기 처리란, 작업을 순차적으로 처리하는 것을 말한다.

자바스크립트 코드는 기본적으로 동기적으로 처리된다.
동기적으로 처리되는 코드는 위에서부터 아래로 순차적으로 실행되며, 어떤 작업이 끝나야 다음 작업을 수행할 수 있다.

하지만, 동기적으로 처리되는 코드는 작업이 끝날 때까지 다른 작업을 수행할 수 없다는 단점이 있다.

console.log('은행 1번 번호표 업무 시작')
console.log('은행 1번 번호표 업무 끝')

console.log('은행 2번 번호표 업무 시작')
console.log('은행 2번 번호표 업무 끝')

- 비동기 처리

비동기 처리는 이러한 단점을 보완하기 위해 등장했다.
비동기 처리는 작업을 동시에 처리할 수 있으며, 작업이 끝나지 않아도 다른 작업을 수행할 수 있다.

setTimeout 함수를 사용하면 비동기 처리를 할 수 있다.
setTimeout은 비동기적으로 동작하는 함수로, 일정 시간이 지난 후에 콜백 함수를 실행한다.

console.log('악마 음료 슈렉 프라푸치노 주문')

setTimeout(() => {
    console.log('아메리카노 주문')
    console.log('아메리카노 주문 완료')
}, 3000)

console.log('악마 음료 슈렉 프라푸치노 주문 완료')

위 코드를 실행하면, '악마 음료 슈렉 프라푸치노 주문'이 먼저 출력되고, 3초 후에 '아메리카노 주문'과 '아메리카노 주문 완료'가 출력된다.

1. setTimeout으로 비동기 처리

  • Vanilla JavaScript
// 데이터를 가져오는 함수
function orderCoffee(coffee, time) {
    console.log(`${coffee}를 만드는 중...`)

    // setTimeout 함수를 사용하여 3초 후에 콜백 함수를 실행
    setTimeout(() => {
        // 콜백 함수에서 커피를 만드는 코드
        console.log(`${coffee}가 만들어졌습니다.`)
    }, time)
}

orderCoffee('카페모카', 6000)
orderCoffee('아메리카노', 4000)
orderCoffee('카페라떼', 5000)
  • React
// src/OrderCoffee.js
import React from 'react'

function OrderCoffee({ coffee, time }) {
    console.log(`${coffee}를 만드는 중...`)

    // setTimeout 함수를 사용하여 3초 후에 콜백 함수를 실행
    setTimeout(() => {
        // 콜백 함수에서 커피를 만드는 코드
        console.log(`${coffee}가 만들어졌습니다.`)
    }, time)

    return <div>{coffee}</div>
}

export default OrderCoffee
// src/App.js
import React from 'react'
import OrderCoffee from './OrderCoffee'

function App() {
    return (
        <div>
            <OrderCoffee coffee="아메리카노" time={4000} />
            <OrderCoffee coffee="카페라떼" time={5000} />
            <OrderCoffee coffee="카페모카" time={6000} />
        </div>
    )
}

export default App

위 코드를 실행하면, 아메리카노가 만들어지는데 4초, 카페라떼가 만들어지는데 5초, 카페모카가 만들어지는데 6초가 걸린다.
비동기 작업은 동기 작업과는 달리 작업의 실행 순서와 완료 순서가 다를 수 있다.

2. 콜백 함수로 비동기 처리

비동기 처리를 하기 위해 콜백 함수를 사용할 수 있다.
콜백 함수는 다른 함수의 인자로 전달되는 함수로, 특정 작업이 끝나면 실행되는 함수이다.

- 동기처리

function orderCoffee(coffee) {
    console.log(`${coffee}를 만드는 중...`)

    setTimeout(() => {
        console.log(`${coffee}가 만들어졌습니다.`)
    }, 3000)
}

function drinkCoffee() {
    console.log('커피를 마신다.')
}

orderCoffee('아메리카노')
drinkCoffee()

- 비동기처리

  • Vanilla JavaScript
// 콜백 함수를 사용하여 비동기 처리하기
function orderCoffee(coffee, callback) {
    console.log(`${coffee}를 만드는 중...`)

    setTimeout(() => {
        console.log(`${coffee}가 만들어졌습니다.`)
        callback()
    }, 3000)
}

// 콜백 함수
function drinkCoffee() {
    console.log('커피를 마신다.')
}

// orderCoffee 함수를 호출하고 콜백 함수를 전달
// 콜백 함수는 orderCoffee 함수에서 커피가 만들어진 후에 실행된다.
orderCoffee('아메리카노', drinkCoffee)
  • React
// src/OrderCoffee.js
import React from 'react'

function OrderCoffee({ coffee, time, callback }) {
    console.log(`${coffee}를 만드는 중...`)

    setTimeout(() => {
        console.log(`${coffee}가 만들어졌습니다.`)
        callback()
    }, time)

    return <div>{coffee}</div>
}

export default OrderCoffee
// src/App.js
import React from 'react'
import OrderCoffee from './OrderCoffee'

function App() {
    return (
        <div>
            <OrderCoffee coffee="아메리카노" time={4000} callback={() => console.log('커피를 마신다.')} />
            <OrderCoffee coffee="카페라떼" time={5000} callback={() => console.log('커피를 마신다.')} />
            <OrderCoffee coffee="카페모카" time={6000} callback={() => console.log('커피를 마신다.')} />
        </div>
    )
}

export default App

위 코드를 실행하면, 아메리카노가 만들어지는데 3초가 걸린 후에 '커피를 마신다.'가 출력된다.

3. Promise로 비동기 처리하기

Promise는 비동기 처리를 위한 객체로, 프로미스를 이용하면 콜백 함수를 사용하지 않고도 더 쉽게 비동기 처리를 할 수 있다.

프로미스는 세 가지 상태를 가진다.

  • 대기(pending) : 비동기 처리가 아직 수행되지 않은 상태
  • 이행(fulfilled) : 비동기 처리가 성공적으로 수행된 상태
  • 거부(rejected) : 비동기 처리가 실패한 상태

프로미스를 사용하여 비동기 처리를 하려면, new Promise()를 사용하여 프로미스 객체를 생성하고, resolve와 reject를 사용하여 프로미스의 상태를 변경한다.

- 프로미스 객체 생성

  • Vanilla JavaScript
// 프로미스 객체 생성
// new Promise((resolve, reject) => {...})를 통해 새로운 프로미스 객체 생성
// 이 객체는 비동기 작업을 수행할 콜백 함수를 인자로 받으며,
// 이 콜백 함수는 resolve와 reject라는 두 개의 함수를 매개변수로 가집니다.
// resolve, reject 함수는 프로미스의 상태를 변경하는 역할을 합니다.
const coffeeOrder = new Promise((resolve, reject) => {
    // 커피 제조 과정을 시뮬레이션하기 위해 setTimeout 함수를 사용합니다.
    // 이는 바리스타가 커피를 제조하는 데 시간이 걸리는 것을 나타냅니다.
    setTimeout(() => {
        // 바리스타가 커피를 성공적으로 제조했다고 가정하고, resolve 함수를 호출합니다.
        // '아메리카노 준비 완료!'라는 메시지와 함께 resolve 함수를 호출합니다.
        resolve('아메리카노 준비 완료!')
    }, 3000) // 3초 후에 커피 제조가 완료됩니다.
})

// 프로미스 객체 사용
coffeeOrder
    // then 메서드를 사용하여 커피가 성공적으로 제조되었을 때의 처리를 작성합니다.
    .then((coffee) => {
        // 커피 제조가 성공적으로 완료되면 고객에게 제공될 커피의 이름을 출력합니다.
        // coffee 매개변수에는 resolve 함수에서 전달한 '아메리카노 준비 완료!'가 전달됩니다.
        console.log(coffee) // '아메리카노 준비 완료!'가 출력됩니다.
    })
    // catch 메서드를 사용하여 커피 제조에 실패했을 때의 처리를 작성합니다.
    .catch((error) => {
        // 커피 제조가 실패하면 에러 메시지를 출력합니다.
        console.error(error)
    })
  • React
// src/OrderCoffee.jsx
import React from 'react'

function OrderCoffee({ coffee, time }) {
    // 프로미스 객체 생성
    const coffeeOrder = new Promise((resolve, reject) => {
        console.log(`${coffee}를 만드는 중...`)

        setTimeout(() => {
            console.log(`${coffee}가 만들어졌습니다.`)
            resolve('아메리카노 준비 완료!') // 성공적으로 커피를 만들었을 때 resolve 함수 호출
        }, time)
    })

    // 프로미스 객체 사용
    coffeeOrder
        // 성공적으로 커피를 만들었을 때 실행할 코드
        .then((coffee) => {
            console.log(coffee)
        })
        // 커피를 만들지 못했을 때 실행할 코드
        .catch((error) => {
            console.error(error)
        })

    return <div>{coffee}</div>
}

export default OrderCoffee
// src/App.js
import React from 'react'
import OrderCoffee from './OrderCoffee'

function App() {
    return (
        <div>
            <OrderCoffee coffee="아메리카노" time={4000} />
            <OrderCoffee coffee="카페라떼" time={5000} />
            <OrderCoffee coffee="카페모카" time={6000} />
        </div>
    )
}

export default App

위 코드는 비동기 작업이 성공적으로 완료되기를 기다렸다가, 완료되면 결과를 처리하는 전형적인 패턴을 보여줍니다. 프로미스를 사용함으로써 비동기 작업의 성공 또는 실패에 따른 처리를 체계적이고 가독성 좋게 구현할 수 있습니다.

- 50% 확률로 성공 또는 실패를 시뮬레이션하는 코드

  • Vanilla JavaScript
const coffeeOrder = new Promise((resolve, reject) => {
    setTimeout(() => {
        // 50% 확률로 성공 또는 실패를 시뮬레이션
        if (Math.random() > 0.5) {
            resolve('아메리카노 준비 완료!')
        } else {
            reject('커피를 준비하지 못했습니다. 죄송합니다.')
        }
    }, 3000)
})

coffeeOrder
    .then((coffee) => {
        console.log(coffee)
    })
    .catch((error) => {
        console.error(error)
    })
  • React
// src/OrderCoffee.jsx
import React from 'react'

function OrderCoffee({ coffee, time }) {
    const coffeeOrder = new Promise((resolve, reject) => {
        // 프로미스 객체 생성
        console.log(`${coffee}를 만드는 중...`)

        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve('아메리카노 준비 완료!')
            } else {
                reject('커피를 준비하지 못했습니다. 죄송합니다.')
            }
        }, time)
    })

    coffeeOrder
        .then((coffee) => {
            console.log(coffee)
        })
        .catch((error) => {
            console.error(error)
        })

    return <div>{coffee}</div>
}

export default OrderCoffee

뉴스 API 데이터 가져오기

- 뉴스 API 키 발급

  1. News API에 접속하여 회원가입을 한다.
  2. API Key를 발급받는다.

- Fetch API로 데이터 가져오기

// src/News.js
import React, { useEffect, useState } from 'react'

function News() {
    const [news, setNews] = useState([])

    useEffect(() => {
        fetch('https://newsapi.org/v2/top-headlines?country=kr&apiKey=API_KEY')
            .then((response) => response.json())
            .then((data) => {
                console.log(data)
                setNews(data.articles)
            })
            .catch((error) => console.error(error))
    }, [])

    return (
        <div>
            <h1>뉴스</h1>
            <ul>
                {news.map((article, index) => (
                    <li key={index}>
                        <a href={article.url} target="_blank" rel="noreferrer">
                            <img src={article.urlToImage} alt="" />
                            {article.title}
                        </a>
                    </li>
                ))}
            </ul>
        </div>
    )
}

export default News

- API Key 숨기기

API Key는 노출되면 안 되는 중요한 정보이므로, API Key를 숨기기 위해 .env 파일을 사용한다. 적용 후 서버를 재시작해야 한다.

  1. 프로젝트 루트 디렉토리에 .env 파일에서 환경 변수를 설정한다.
  2. .env 파일에 API Key를 저장한다.
// .env
REACT_APP_NEWS_API_KEY=API_KEY
  1. .env 파일에 저장한 API Key를 process.env.REACT_APP_NEWS_API_KEY로 시작하는 이름으로 사용한다.
// src/News.js
import React, { useEffect, useState } from 'react'

function News() {
    const [news, setNews] = useState([])

    useEffect(() => {
        // .env 파일에 저장한 API Key를 사용
        fetch(`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${process.env.REACT_APP_NEWS_API_KEY}`)
            (...)
    }, [])

    return (
        (...)
    )
}

export default News

4. async/await로 비동기 처리하기

async/await는 프로미스를 더 쉽게 사용할 수 있도록 해주는 ES8(ECMAScript 2017)의 문법으로 비동기 처리를 더 간결하게 작성할 수 있다.

  • async : 함수 앞에 async 키워드를 붙이면, 해당 함수는 항상 프로미스를 반환한다.
  • await : await 키워드는 async 함수 내부에서만 사용할 수 있으며, 프로미스가 처리될 때까지 기다린 후 결과를 반환한다.
// src/News.js
import React, { useEffect, useState } from 'react'

function News() {
    const [news, setNews] = useState([])

    useEffect(() => {
        // async/await로 비동기 처리하기
        const fetchData = async () => {
            // try: 정상적으로 실행되는 코드
            // catch : 에러가 발생했을 때 실행할 코드
            // fetch 함수를 사용하여 뉴스 데이터를 가져옵니다.
            try {
                const response = await fetch(`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${process.env.REACT_APP_NEWS_API_KEY}`) // API Key를 사용
                const data = await response.json() // JSON 형태로 변환
                console.log(data) // 데이터 확인
                setNews(data.articles) // 뉴스 데이터를 상태로 저장
            } catch (error) {
                console.error(error)
            }
        }

        fetchData()
    }, [])

    return (
        (...)
    )
}

export default News

5. axios로 데이터 가져오기

axios는 브라우저와 Node.js를 위한 Promise 기반 HTTP 클라이언트로, 비동기 방식으로 HTTP 데이터 요청을 수행할 수 있다.
axios를 사용하는 이유는 fetch API보다 더 간결하고 직관적이며, 브라우저 호환성이 더 좋기 때문이다.

- axios 설치

$ npm install axios

- axios로 데이터 가져오기

// src/News.js
import React, { useEffect, useState } from 'react'
import axios from 'axios'

function News() {
    const [news, setNews] = useState([])

    useEffect(() => {
        // axios로 데이터 가져오기
        const fetchData = async () => {
            try {
                const response = await axios.get(`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${process.env.REACT_APP_NEWS_API_KEY}`)
                // data 속성에 뉴스 데이터가 들어있음
                console.log(response.data)
                setNews(response.data.articles)
            } catch (error) {
                console.error(error)
            }
        }

        fetchData()
    }, [])

    return (
        (...)
    )
}

export default News

6. 비동기 처리의 순서

비동기 처리를 할 때, 작업의 순서를 보장하기 위해 async/await를 사용할 수 있다.

// src/News.js
import React, { useEffect, useState } from 'react'
import axios from 'axios'

function News() {
    const [news, setNews] = useState([])

    useEffect(() => {
        const fetchData = async () => {
            try {
                // 첫 번째 요청
                const response1 = await axios.get(`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${process.env.REACT_APP_NEWS_API_KEY}`)
                console.log(response1.data)
                setNews(response1.data.articles)

                // 두 번째 요청
                const response2 = await axios.get(`https://newsapi.org/v2/top-headlines?country=us&apiKey=${process.env.REACT_APP_NEWS_API_KEY}`)
                console.log(response2.data)
            } catch (error) {
                console.error(error)
            }
        }

        fetchData()
    }, [])

    return (
        (...)
    )
}

export default News

위 코드를 실행하면, 한 번에 두 개의 요청을 보내고, 첫 번째 요청이 완료된 후 두 번째 요청이 실행된다.

7. Promise.all로 여러 개의 프로미스 처리하기

Promise.all은 여러 개의 프로미스를 동시에 처리할 수 있는 메서드로, 모든 프로미스가 성공적으로 처리되었을 때 결과를 반환한다.

// src/News.js
import React, { useEffect, useState } from 'react'
import axios from 'axios'

function News() {
    const [news, setNews] = useState([])

    useEffect(() => {
        const fetchData = async () => {
            try {
                // Promise.all로 여러 개의 프로미스 처리하기
                const [response1, response2] = await Promise.all([
                    axios.get(`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${process.env.REACT_APP_NEWS_API_KEY}`),
                    axios.get(`https://newsapi.org/v2/top-headlines?country=us&apiKey=${process.env.REACT_APP_NEWS_API_KEY}`)
                ])

                console.log(response1.data)
                console.log(response2.data)
            } catch (error) {
                console.error(error)
            }
        }

        fetchData()
    }, [])

    return (
        (...)
    )
}

export default News

위 코드를 실행하면, 두 개의 요청을 동시에 보내고, 두 요청이 모두 완료되면 결과를 반환한다.

반응형
티스토리 친구하기