[DAY 12] VanillaJS를 통한 JS 기본 역량 강화(4)
[1] ES6 Module 사용하기
module을 사용하는 경우, script에 type=”module”을 적어줘야 합니다!
[1-1] import
import는 export 키워드로 내보내진 변수, 함수 등을 불러올 수 있는 키워드입니다.
- module-name 내에 export default로 내보내진 것을 가져옵니다.
import default Export from "module-name";
- module-name 내에서 export 된 모든 것을 모두 가져옵니다.
- as 이후 이름은 중복되지만 않으면 자유롭게 정할 수 있습니다.
import * as allItems from "module-name";
- module-name 내에서 export 된 것 중에 특정 값만 가져옵니다.
import { loadItem } form "module-name";
- module-name 내에 export 된 것 중에 특정 값만 이름을 바꿔서 가져옵니다.
import {
loadItem as loadSomething
} from "module-name";
- export defult 된 것과 개별 export 된 것을 한 번에 가져올 수 있습니다.
import default Function, {
loadItem
} from "module-name";
- 별도의 모듈 바인딩 없이 불러오기만 합니다.
- 불러오는 것만으로 효과가 있는 스크립트의 경우 사용됩니다.
import "module-name";
→ 이렇게 import를 사용하면 스크립트의 의존성을 훨씬 간편하게 관리할 수 있습니다!!
[1-2] Simple Todo App에 적용하기
이렇게 하면 무엇이 좋은 걸까요?🤔
- 각 JS별로 사용되는 모듈을 명시적으로 import 해오기 때문에, 사용되거나 사용되지 않는 스크립트를 추적할 수 있습니다.
- script 태그로 로딩하는 경우 불러오는 순서가 중요하지만, import로 불러오는 경우 순서는 무관합니다.
- script src로 불러오는 것과 다르게, 전역오염이 발생하지 않습니다.
[+] import를 사용하려면 웹 서버가 필요합니다.
→ 하지만 지금은 serve 모듈로 로컬 웹서버를 띄워서 진행하기 때문에 상관없습니다!
[+] 뭔가 잘 안 된다면 일단 from 이후 모듈 이름 맨 뒤에 .js를 잘 적었는지 확인해보세요!
[2] 비동기 다루기 - callback
[2-1] 비동기 처리란?
특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고, 다음 코드를 먼저 실행하는 JS의 특성을 이야기합니다!
[2-2] 첫 번째 예시! addEventListener 함수
- addEventListener 함수는 두 번째 인자로 넘겨진 함수는 바로 실행되지 않고, 이벤트 리스너가 정의한 이벤트가 발생할 때 실행됩니다.
function onButtonClick() {
alert('눌렀군요!');
}
document.querySelector('.save-button').addEventListener('click', onButtonClick);
[2-3] 두 번째 예시! setTimeout과 setInterval
- setTimeout과 setInterval은 첫 번째 인자로 넘겨진 함수는 바로 실행되지 않고, setTimeout 또는 setInterval의 시간만큼 지난 후에 실행됩니다.
- setTimeout의 시간을 0 또는 지정하지 않아도 바로 실행되지 않습니다!
→ 함수가 모두 실행된 이후에 실행됩니다.
function work() {
console.log('work!');
}
setTimeout(work, 1000);
setTInterval(work, 5000);
[2-4] 세 번째 예시! XMLHttpRequest(XHR)
- 데이터를 비동기로 요청하고, 요청 후의 동작을 비동기로 처리합니다.
function request(url, successCallback, failCallback) {
const xhr = new XMLHttpRequest();
xhr.addEventListener("load", (e) => {
if (xhr.readyState === 4) {
successCallback(JSON.parse(xhr.responseText));
} else {
failCallback(xhr.statusText);
}
});
xhr.addEventListener("error", (e) => failCallback(xhr.statusText));
xhr.open("GET", url);
xhr.send();
}
- 앞에서 정의한 request 함수를 이용해서 다음의 시나리오를 처리하려면??
- https://~~/todos 를 조회합니다.
- 조회를 성공했다면, todos 목록중 isCompleted가 true인 todo의 id로 comments를 불러옵니다.
https://~~/comments?todo.id={todoId} - 2에서 불러온 데이터 중 content를 화면에 그립니다.
// async
const API_ENDPORT = "https://~~";
request(`${API_ENDPOINT}/todos`, (todos) => {
const completeTodo = todos.find((todo) => todo.isCompleted);
if (completeTodo) {
request(`${API_ENDPOINT}/comments?todo.id=${completedTodo.id}`, (comments) => {
comments.forEach((comment) => console.log(comment.content));
});
}
});
→ 이런 경우, 일반적인 callback 패턴인 경우는 callback이 중첩됩니다.
그런데 만약 순차적으로 처리해야 하는 비동기 작업이 더 많아지면 어떻게 될까요?
→ 그것이 바로 유명한 callback hell 입니다.
- XMLHttpRequest의 경우 비동기를 포기하고, 동기 방식으로 동작하게 되면 아래처럼도 가능합니다!
function syncRequest(url) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, false); // 동기방식으로 처리하도록 false를 넣어줍니다!
xhr.send();
if (xhr.status === 200) {
return JSON.parse(xhr.responseText);
} else {
throw new Error(xhr.statusText);
}
}
// sync
const API_ENDPORT = "https://~~";
const todos = syncRequest(`${API_ENDPOINT}/todos`)
const completeTodo = todos.find((todo) => todo.isCompleted);
if (completeTodo) {
const comments = syncRequest(`${API_ENDPOINT}/comments?todo.id=${completedTodo.id}`)
comments.forEach((comment) => console.log(comment.content));
}
→ 코드의 depth가 깊어지지 않아, 가독성도 좋고 이해하기도 더 편합니다.
하지만 sync 방식을 사용하게 되면, 요청 후 응답이 오기 전까지 브라우저가 굳어버립니다.
(만약 API 조회에 10초가 걸린다면 10초간 브라우저가 먹통이 되버릴 것입니다.)
[3] Promise
[3-1] Promise란?
Promise는 비동기 작업을 제어하기 위해 나온 개념으로, callback hell에서 어느 정도 벗어날 수 있게 해줍니다.
promise로 정의된 작업끼리는 연결할 수 없으며, 이를 통해 코드의 depth가 크게 증가하지 않는 효과가 있습니다.
- Promise 만들기
const promise = new Promise((resolve, reject) => {
// promise 내부에서 비동기 상황이 종료될 대, resolve 함수 호출
// promise 내부에서 오류 상황일 때, reject 함수 호출
})
- Promise에서는 then을 이용해 비동기 작업 이후 실행할 작업을 지정합니다.
function asyncPromiseWork() {
// some code ...
return new Promise((resolve, reject) => {
// some code ...
return resolve('complete')
})
}
// then의 result에는 resolve를 호출하면 넘긴 complete가 들어있음
asyncPromiseWork().then(result => console.log(result))
- Promise의 then 내에서 promise를 return할 경우 이어집니다.
promiseWork()
.then(result => {
return pormiseNextWork(result)
}).then(result => {
return promiseThirdWork(result)
}).then(result => {
return promiseFinalWork(result)
})
- Promise chain 중 작업이 실패했을 경우, .catch로 잡을 수 있습니다.
- catch를 중간에 넣고 이후 작업을 연결해도 동작합니다.
- catch를 넣지 않을 경우, promise chain 중 에러가 발생했을 때 chain이 멈추니 가급적 넣는게 좋습니다.
promiseWork()
.then(result => {
return promiseErrorWork(result)
}).then(result => {
return promiseThirdWork(result)
}).then(result => {
return promiseFinalWork(result)
}).catch(e => {
alert('저런! 에러가 발생했네용')
})
- 성공과 실패 여부 상관없이 호출해야 하는 코드가 있다면 finally에서 처리합니다.
promiseWork()
.then(result => {
return promiseErrorWork(result)
}).then(result => {
return promiseThirdWork(result)
}).then(result => {
return promiseFinalWork(result)
}).catch(e => {
alert('저런! 에러가 발생했네용')
}).finally(() => {
alert('어쨌든 작업은 끝났습니닷')
})
- 기존의 callback 함수를 promise 형태로 만들 수 있습니다.
- resolve를 작업이 끝나는 순간에 호출하면 됩니다.
const delay = (delayTune) => new Promise((resolve) {
setTimeout(resolve, delayTime)
})
delay(5000)
.then(() => {
doSomething()
return delay(3000)
).then(() => {
console.log('complete!!')
})
[3-2] Promise 내장함수
- Promise 처리된 delay와 조합하기
request(`${API_END_POINT}/todos`)
.then((data) => {
this.setState({
isLoading: flase,
todoList: data,
comments: []
})
return delay(5000)
}).then(() => {
console.log('completed!!')
})
Promise.all(iterable)
: 여러 promise를 동시에 처리할 때 유용합니다.
const promise1 = delay(1000)
const promise2 = delay(1000)
const promise3 = delay(1000)
Promise.all([promise1, promise2, promise3]).then(() => {
// promise1, promise2, promise3이 모두 처리된 이후 호출
})
Promise.race(iterable)
: 여러 promise 중 하나라도 resolve 혹은 reject 되면 종료됩니다.
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
const promises = [1, 2, 3, 4, 5].map( n => {
const delayTime = getRandomInt(1000, 5000)
return new Promise(resolve => {
setTimeout(() => {
console.log(`${n}번 고양이 완주!`)
resolve(`${n}번 고양이 승리!`)
},delayTime)
})
})
Promise.race(promises).then(message => console.log(message))
Promise.any(iterable)
: 여러 promise 중 하나라도 resolve 되면 종료됩니다.
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
const promises = [1, 2, 3, 4, 5].map( n => {
const delayTime = getRandomInt(1000, 5000)
return new Promise(resolve => {
if(n===1) {
return reject(`${n}번 고양이 기권!`)
}
setTimeout(() => {
console.log(`${n}번 고양이 완주!`)
resolve(`${n}번 고양이 승리!`)
},delayTime)
})
})
Promise.allSettled(iterable)
: 여러 promise들이 성공했거나 실패했거나 상관 없이 모두 이행된 경우 처리할 수 있습니다.
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
const promises = [1, 2, 3, 4, 5].map( n => {
const delayTime = getRandomInt(1000, 5000)
return new Promise((resolve, reject) => {
if(n % 2 === 0) {
return reject(`${n}번 고양이 완주 실패 T_T`)
}
setTimeout(() => {
resolve(`${n}번 고양이 승리!`)
},delayTime)
})
})
Promise.resolve
: 주어진 값으로 이행하는 Promise.then 객체를 만듭니다. 주어진 값이 Promise인 경우 해당 Promise가 반환됩니다.
const cached = {
'jieun' : 'bassist'
}
const findMember = (memberName) => {
if (cached[memberName]) {
return Promise.resolve(cached[memberName])
}
return rerquest(`/members/${memberName}`).then((member) => {
cache[member.memberName] = memberName
return memberName
})
findMember('jieun').then((memberName) => console.log(memberName)
Promise.reject
: 주어진 값으로 reject처리된 Promise.then 객체를 만듭니다. 주어진 값이 Promise인 경우 해당 Promise가 반환됩니다. → 사용할 일이 거의 없을 겁니당
[4] async, await
Promise가 callback depth를 1단계로 줄여주긴 하지만, 여전히 불편한 것은 사실입니다.
여기에 async, await를 이용하면 Promise를 동기 코드처럼 보이게 할 수 있습니다.
물론 실행은 여전히 비동기로 실행됩니다!
const delay = (delayTime) => {
return new Promise(resolve => setTimeout(resolve, delayTime))
}
// promise 사용
const work = () => {
console.log('work run')
delay(1000)
.then(() => {
console.log('work 1 compelet.')
return delay(1000)
})
.then(() => {
console.log('work 2 compelet.')
return delay(1000)
})
.then(() => {
console.log('work 3 compelet.')
return delay(1000)
})
.then(() => {
console.log('work all complete!')
})
consoel.log('work running...')
}
work()
// async, await 사용
const work = () => {
console.log('work run')
await delay(1000)
console.log('work 1 compelet.')
await delay(1000)
console.log('work 2 compelet.')
await delay(1000)
console.log('work 3 compelet.')
await delay(1000)
console.log('work all complete!')
}
work()
- 다음과 같이 두 가지 방식이 있습니다.
async function asyncFunction () {
const res = await request(...)
}
const asyncFunction = async () {
const res = await request(...)
}
- async 키워드 함수가 붙은 함수는 실행 결과를 Promise로 감싸서 출력합니다.
→ async 키워드는 결국 promise 내에서 돌아간다는 것이 중요합니다!
- 기본적으로 await는 async로 감싸진 함수 스코프에서만 사용이 가능했지만, top level await가 등장하여 top level에서도 사용 가능합니다!
Author And Source
이 문제에 관하여([DAY 12] VanillaJS를 통한 JS 기본 역량 강화(4)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jieun0411/DAY-12-VanillaJS를-통한-JS-기본-역량-강화4저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)