🔷 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성
한 연산이 끝나야 다음 연산이 돌아가는 순서를 지키는 동기 실행 코드와 다르게 순서를 이리저리 바꾸거나 동시 실행이 가능하다.
🖨 예시 1
// 비동기 실행 ex) addEventListener()
// 해당 함수의 두 번째 인자로 넘겨진 함수는 바로 실행되지 않고 이벤트 리스너가 정의한 이벤트가 발생했을 때 실행
function onButtonClick () {
alert('눌렀다')
}
document.querySelector('.save-button').addEventListener('click', onButtonClick)
// 비동기 실행 ex2) setTimeout(), setInterval()
// 첫 번째 인자로 넘겨진 함수는 바로 실행되지 않고 setTimeout 혹은 setInterval의 시간만큼 지난 후에 실행
function work() {
console.log('work!')
}
setTimeout(work, 1000) // 한 번 실행
setInterval(work, 5000) // 시간 주기로 반복 실행
console.log('work process')
// 비동기 실행 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초간 브라우저가 먹통이 된다.
🔷 응용 프로그램 프로그래밍 인터페이스. 프로그래밍에서, 프로그램을 작성하기 위한 일련의 부(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()
}
🖥 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 함수와 그 외의 것들은 이번 장에서는 분량의 문제로 다 다루지 못했다.
다음에 마저 다루도록 한다.