
하나의 작업이 끝나기 전에는 다음 작업이 시작되지 않는다.
console.log(1);
console.log(2);
alert("확인!"); // alert 창이 발생하기 전에 log(3) 이후 작업이 진행되지 않음.
console.log(3);
console.time("Loop!");
for (let i = 0; i < 100000000; i++) {}
console.timeEnd("Loop!");
console.log(4);
// 1
// 2
// 3
// Loop!: 68.593017578125 ms
// 4
특정 작업이 끝나기 전에 다음 작업이 시작될 수 있다.
console.log(1);
console.log(2);
console.log(3);
console.time("Loop!");
setTimeout(() => {
for (let i = 0; i < 100000000; i++) {}
console.timeEnd("Loop");
}, 0);
// 1
// 2
// 3
// 4
// Loop!: 45.551025390625 ms
// 5
console.log(1);
const h1El = document.querySelector("h1");
// 사용자가 클릭하지 않으면 실행되지 않는다. 비동기 방식으로 실행.
h1El.addEventListener("click", () => {
console.log("클릭!");
});
console.log(2);
console.log(1);
// 서버와의 통신도 병렬로 즉, 비동기 방식으로 실행이 된다.
fetch("https://api.heropy.dev/v0/users")
.then((res) => res.json())
.then((data) => console.log(data));
console.log(2);
따라하기
특정한 비동기 함수를 호출할 때 콜백함수를 전달하면서 어느 위치에서 실행할지 지정해줄 수 있다.
내가 지정된 위치해서 실행되기 때문에 원하는 동작을 만들 수 있다.
// main.js
import { timer } from "./timer.js";
timer(() => {
console.log(2); // 두 번째 실행
});
// timer.js
export function timer(callback) {
setTimeout(() => {
console.log(1); // 첫 번째 실행
callback();
}, 2000);
}
따라하기
기존의 콜백패턴(콜백지옥)의 개선
// 콜백패턴
function renderImage(callback) {
const imgEl = document.createElement("img");
imgEl.src = "https://picsum.photos/2000/1000";
imgEl.addEventListener("load", () => {
document.body.append(imgEl);
callback();
});
}
// 비동기 코드를 동기 방식으로 사용하며 안쪽으로 계속 들여쓰여짐 (콜백지옥 - 가독성 떨어짐 유지보수 불편)
renderImage(() => {
console.log("Done 1");
renderImage(() => {
console.log("Done 2");
renderImage(() => {
console.log("Done 3");
renderImage(() => {
console.log("Done 4");
});
});
});
});
Promise를 활용한 개선코드
function renderImage() {
return new Promise((resolve) => {
const imgEl = document.createElement("img");
imgEl.src = "https://picsum.photos/2000/1000";
imgEl.addEventListener("load", () => {
document.body.append(imgEl);
resolve();
});
});
}
// 가독성이 좋아짐.. then과 Promise 와의 관계
renderImage()
.then(() => {
console.log("Done 1");
return renderImage();
})
.then(() => {
console.log("Done 2");
return renderImage;
})
.then(() => {
console.log("Done 3");
return renderImage;
})
.then(() => {
console.log("Done 4");
});
비동기 작업의 완료나 실패 시점을 지정하고 그 결과를 반환할 수 있다.
const promise = new Promise((resolve(성공), reject(실패))=>{})
Promise와 then을 사용하는 방식
function loadImage(src) {
return new Promise((resolve) => {
const imgEl = document.createElement("img");
imgEl.src = src;
imgEl.addEventListener("load", () => {
resolve(imgEl); // 원하는 시점에서 호출(특정 시점에서 약속을 이행한다.)(반환결과)
});
});
}
// .then() 약속이 이행되면 then 메소드를 호출한다.
loadImage("https://picsum.photos/2000/1000").then((imgEl) => {
document.body.append(imgEl);
console.log("Done 1");
});
loadImage("https://picsum.photos/100/200").then((imgEl) => {
console.log(imgEl);
});
async와 await 를 사용하는 방식
// 비동기
function loadImage(src) {
return new Promise((resolve) => {
const imgEl = document.createElement("img");
imgEl.src = src;
imgEl.addEventListener("load", () => {
resolve(imgEl); // 원하는 시점에서 호출(특정 시점에서 약속을 이행한다.)(반환결과)
});
});
}
// async와 await 를 사용하는 방식
(async () => {
// 즉시실행함수
const imgEl = await loadImage("https://picsum.photos/2000/1000");
document.body.append(imgEl);
console.log("Done 1");
const imgEl2 = await loadImage("https://picsum.photos/100/200");
console.log(imgEl2);
})();
서버에서 데이터를 가져오고 딜레이되는 시간에 에니메이션을 작동하기
따라하기
// Async-Await
const h1El = document.querySelector("h1");
const ulEl = document.createElement("ul");
ulEl.classList.add("users");
document.body.append(ulEl);
// await는 async와 항상 함께쓴다. 비동기
h1El.addEventListener("click", async () => {
const loaderEl = document.createElement("div");
loaderEl.classList.add("loader");
ulEl.innerHTML = ""; // 빈 문자로 내용을 비우고나서 다시 채워넣는다.
ulEl.append(loaderEl);
// fetch 함수 호출을 통해서는 promise 인스턴스가 반환됨 // await : 데이터를 전달받을 떄 까지 기다렸다가 실행
const res = await fetch("https://api.heropy.dev/v0/users");
const data = await res.json();
console.log(data); // 데이터를 가져온 결과
const { users } = data; // 배열데이터
const liEls = users.map((user) => {
// .map : 콜백에서 반환하는 데이터를 모아서 새로운 배열을 만듬
const liEl = document.createElement("li");
liEl.textContent = user.name;
liEl.dataset.photo = user.photo?.url || "http://heropy.dev/favicon.png";
if (!user.photo) {
liEl.classList.add("no-photo");
}
const loaderEl = document.createElement("div");
loaderEl.classList.add("loader");
liEl.prepend(loaderEl); // .prepend : li요소에 가장 앞쪽에 이미지요소 추가
return liEl;
});
loaderEl.remove();
ulEl.append(...liEls);
liEls.forEach(async (liEl) => {
const imgEl = await loadImage(liEl.dataset.photo);
liEl.prepend(imgEl);
liEl.querySelector(".loader").remove();
});
});
function loadImage(src) {
return new Promise((resolve) => {
const imgEl = document.createElement("img");
imgEl.src = src;
imgEl.addEventListener("load", () => {
resolve(imgEl);
});
});
}
위 코드에서 예외처리를 포함해 개선한 코드
따라하기 예외처리
// Async-Await
const h1El = document.querySelector("h1");
const ulEl = document.createElement("ul");
ulEl.classList.add("users");
document.body.append(ulEl);
// await는 async와 항상 함께쓴다. 비동기
h1El.addEventListener("click", async () => {
const loaderEl = document.createElement("div");
loaderEl.classList.add("loader");
ulEl.innerHTML = ""; // 빈 문자로 내용을 비우고나서 다시 채워넣는다.
ulEl.append(loaderEl);
try {
// 예외처리 try() catch(){} 키워드를 활용.
// fetch 함수 호출을 통해서는 promise 인스턴스가 반환됨 // await : 데이터를 전달받을 떄 까지 기다렸다가 실행
const res = await fetch("https://api.heropy.dev/v0/users");
const data = await res.json();
console.log(data); // 데이터를 가져온 결과
const { users } = data; // 배열데이터
const liEls = users.map((user) => {
// .map : 콜백에서 반환하는 데이터를 모아서 새로운 배열을 만듬
const liEl = document.createElement("li");
liEl.textContent = user.name;
liEl.dataset.photo = user.photo?.url || "http://heropy.dev/favicon.png";
if (!user.photo) {
liEl.classList.add("no-photo");
}
const loaderEl = document.createElement("div");
loaderEl.classList.add("loader");
liEl.prepend(loaderEl); // .prepend : li요소에 가장 앞쪽에 이미지요소 추가
return liEl;
});
loaderEl.remove();
ulEl.append(...liEls);
liEls.forEach(async (liEl) => {
try {
const imgEl = await loadImage(liEl.dataset.photo);
liEl.prepend(imgEl);
} catch (error) {
console.log(error);
} finally {
// 정상적으로 동작할 때와 그렇지 않을 때를 동일하게 만들어주기 위해 finally 구문을 추가적으로 활용
liEl.querySelector(".loader").remove();
}
});
} catch (error) {
// try 부분에서 예외상황(에러 등)이 생기면 바로 catch 부분으로 내려옴
console.log(error);
ulEl.textContent = "사용자 정보를 찾을 수 없어요..";
loaderEl.remove();
}
});
function loadImage(src) {
return new Promise((resolve, reject) => {
const imgEl = document.createElement("img");
imgEl.src = src;
imgEl.addEventListener("load", () => {
resolve(imgEl);
});
imgEl.addEventListener("error", () => {
// 약속을 이행하지 못했을 떄의 상황(reject)
reject(new Error("이미지를 로드할 수 없어요.."));
});
});
}
Pending - 약속이 이행되거나 거부되기 직전의 상태(대기)
Fulfilled - 약속이 이행된 상태(이행)
Rejected - 약속이 거부된 상태(거부)
.then() - 약속이 이행되었을 떄 호출(then)하거나,
.catch() - 약속이 거부되었을 때 호출(catch)하거나,
.finally() - 이행 및 거부와 상관없이 항상 호출(finally)하는 메소드를 제공
loadImage("https://picsum.photo/300")
.then((imgEl) => {
document.body.append("imgEl");
})
.catch((error) => {
console.log(error.message);
})
.finally((imgEl) => {
console.log("Done!");
});
try - 에러(예외)가 발생할 수 있는 코드의 실행을 시도(try)하고,
catch - 에러가 발생하면 시도를 종료해 에러를 잡아내며(catch)
finally - 에러 여부와 상관없이 항상 실행(finally)하는 코드를 정의할 수 있다.
(async () => {
try {
const imgEl = await loadImage("https://picsum.photo/300");
document.body.append(imgEl);
} catch (error) {
console.log(error.message);
} finally {
console.log("Done!");
}
})();
// 요청(Request)
// 클라이언트 ---------------->
// <---------------- 서버
// 응답(Response)
요청(Request)
응답(Response)
CRUD (method의 정보와 일치)
URL 구조
HTTP 상태 코드
그 외
fetch(url, options)
// 서버의 특정 사용자 데이터를 수정하는 예시
fetch("https://api.heropy.dev/v0/users/ywTTX", {
method: "PUT", // 데이터 수정
headers: {
"Contents-Type": "application/json",
},
body: JSON.stringify({
name: "Neon",
age: 22,
isValid: false,
}),
})
.then((res) => res.json())
.then((data) => console.log(data));