자바스크립트의 비동기 처리

young-gue Park·2023년 1월 31일
0

JavaScript

목록 보기
15/20
post-thumbnail

⚡ 자바스크립트의 비동기 처리


📌 비동기 처리

🔷 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성

  • 한 연산이 끝나야 다음 연산이 돌아가는 순서를 지키는 동기 실행 코드와 다르게 순서를 이리저리 바꾸거나 동시 실행이 가능하다.

  • 🖨 예시 1

// 비동기 실행 ex) addEventListener()
// 해당 함수의 두 번째 인자로 넘겨진 함수는 바로 실행되지 않고 이벤트 리스너가 정의한 이벤트가 발생했을 때 실행
function onButtonClick () {
    alert('눌렀다')
}
document.querySelector('.save-button').addEventListener('click', onButtonClick)
  • 🖨 예시 2
// 비동기 실행 ex2) setTimeout(), setInterval()
// 첫 번째 인자로 넘겨진 함수는 바로 실행되지 않고 setTimeout 혹은 setInterval의 시간만큼 지난 후에 실행
function work() {
    console.log('work!')
}

setTimeout(work, 1000) // 한 번 실행
setInterval(work, 5000) // 시간 주기로 반복 실행

console.log('work process')
  • 🖨 예시 3
// 비동기 실행 ex3) XMLHttpRequest(XHR)
// 데이터를 비동기로 요청하고, 요청 후의 동작을 비동기로 처리
export function request (url, successCallback, failCallback) {
    const xhr = new XMLHttpRequest()
    xhr.addEventListener("load", (e) => {
        if(xhr.readyState ===4) {
            if(xhr.status === 200) {
                successCallback(JSON.parse(xhr.responseText))
            } else {
                failCallback(xhr.statusText)
            }
        }
    })
    xhr.addEventListener('error', (e) => failCallback(xhr.statusText))

    xhr.open('GET', url)
    xhr.send()
}

🤷‍♂️ 비동기의 콜백지옥보다 차라리 더 깔끔한 코드의 동기로 처리하면 안되나요..?
그렇게 사용하게 되면 요청 후 응답이 오기 전까지 브라우저가 굳어버려서 안된다❗
ex) API 조회에 10초가 걸린다면 10초간 브라우저가 먹통이 된다.


📌 API

🔷 응용 프로그램 프로그래밍 인터페이스. 프로그래밍에서, 프로그램을 작성하기 위한 일련의 부(Sub) 프로그램, 프로토콜 등을 정의하여 상호 작용을 하기 위한 인터페이스 사양을 말한다.

  • 소스 코드 수준에서 정의되는 인터페이스다.
  • 비동기 처리 방식을 지원한다.

이번에도 Todo List를 손볼것이다. XMLHTTPRequest를 이용해 불러온 API를 이용하는 방식으로.

💡 XMLHTTPRequest는 요즘은 잘 안쓴다고 한다.

🖥 index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Second Todo List</title>
</head>
<body>
    <main class ="app"></main>
    <script src ="./src/main.js" type="module"></script>
</body>
</html>

🖥 api.js

export function request (url, successCallback, failCallback) {
    const xhr = new XMLHttpRequest()
    xhr.addEventListener("load", (e) => {
        if(xhr.readyState ===4) {
            if(xhr.status === 200) {
                successCallback(JSON.parse(xhr.responseText))
            } else {
                failCallback(xhr.statusText)
            }
        }
    })
    xhr.addEventListener('error', (e) => failCallback(xhr.statusText))

    xhr.open('GET', url)
    xhr.send()
}

🖥 App.js

import TodoList from "./TodoList.js";
import TodoComments from "./TodoComments.js";
import { request } from "./api.js";

export default function App({$app}) {
    this.state = {
        todos: [],
        selectedTodo: null,
        comments:[]
    }

    this.setState = nextState => {
        this.state = nextState
        todoList.setState(this.state.todos)
        todoComments.setState({
            selectedTodo: this.state.selectedTodo,
            comments: this.state.comments
        })
    }

    const todoList = new TodoList({
        $target: $app,
        initialState: this.todos,
        onClick: id => {
            const selectedTodo = this.state.todos.find(todo => todo.id ===id)
            this.setState({
                ...this.state,
                selectedTodo
            })
            request(`https://kdt.roto.codes/comments?todo.id=${id}`, (comments) => {
                this.setState({
                    ...this.state,
                    comments
                })
            })
        }
    })

    const todoComments = new TodoComments({
        $target: $app,
        initialState: {
            selectedTodo: this.state.selectedTodo,
            comments: this.state.comments
        }
    })

    this.init = () => {
        request(`https://kdt.roto.codes/todos`, (todos) => {
            this.setState({
                ...this.state,
                todos
            })
        })
    }
    this.init()
}

이 코드에 사용된 API 링크는 프로그래머스에서 제공하였습니다.

🖥 TodoComments.js

export default function TodoComments({$target, initialState}) {
    const $element = document.createElement('div')
    $target.appendChild($element)

    this.state = initialState

    this.setState = nextState => {
        this.state = nextState
        this.render()
    }

    this.render = () => {
        const {selectedTodo, comments} = this.state

        if(!selectedTodo || !comments) {
            $element.innerHTML = ''
            return
        }
        $element.innerHTML = `
            <h2>${selectedTodo.text}의 댓글들</h2>
            ${comments.length === 0? '댓글이 없습니다' : ''}
            <ul>
                ${comments.map(({content})=> `<li>${content}</li>`).join('')}
            </ul>
        `
    }
    this.render()
}

🖥 TodoList.js

export default function TodoList({$target, initialState, onClick}) {
    const $element = document.createElement('div')
    $target.appendChild($element)

    this.state = initialState

    this.setState = nextState => {
        this.state = nextState
        this.render()
    }

    this.render = () => {
        if(Array.isArray(this.state)) {
        $element.innerHTML = `
            <h1>Simple TodoList</h1>
            <ul>
                ${this.state.map(({id, text}) => `<li data-id="${id}>${text}</li>`).join('')}
            </ul>
        `

        $element.querySelectorAll('li').forEach($li =>  {
                $li.addEventListener('click', (e) => {
                    const {id} = e.target.dataset
                    onClick(parseInt(id,10))
                })
            })
        }
    }
    this.render()
}

🖥 main.js

import App from'./App.js'

const $app = document.querySelector('.app')

new App({$app})

💡 API를 붙이기 전에 main.js에 더미데이터를 넣고 렌더링이 잘 되는지 먼저 확인해보는 것이 좋다.
그래야 API를 붙이는 과정에서의 문제인지 다른 코드에서의 문제인지 확인이 가능하기 때문이다.

🖨 출력 화면


비동기 처리 방식인 promise 함수와 그 외의 것들은 이번 장에서는 분량의 문제로 다 다루지 못했다.
다음에 마저 다루도록 한다.

profile
Hodie mihi, Cras tibi

0개의 댓글