1. 동기(synchronous)와 비동기(asynchronous)
동기란, 호이스팅(선언이 제일 위로 올라가는 것)이 된 이후부터 코드가 작성된 순서대로 작동한다는 의미이다.
동기란 말을 처음 들었을 때는 동기화란 단어가 생각이 나서 "어떤 작업이 동시에 같이 일어나는 것인가?"라 생각을 했었는데, 전혀 다르다.
반면 비동기는 JavaScript의 동기적인 특성인 코드가 작성된 순서를 따르지 않고, 특정 요청에 의해 해당 코드에 지연이 발생하더라도 기다리지 않고 바로 이어서 다음 코드를 실행한다는 의미이다.
// Synchronous
function first() {
console.log("first"); // 첫번째로 실행되고,
}
function second() {
console.log("second"); // 이어서 바로 실행된다.
}
first()
second()
// Asynchronous
function first() {
setTimeout(() => console.log(first), 5000);
// first 함수가 호출되고, 5초 동안 기다린 다음 결과값을 출력하고, 그 다음으로 second 함수가 실행되는게 아니다.
}
function second() {
console.log("second");
// first 함수가 호출되고, 5초의 시간이 흐르는 사이에 second 함수를 실행하고, 이후에 first 함수의 결과값이 출력된다.
}
first()
second()
2. callback(콜백)
callback 함수는 다른 함수가 실행을 끝낸 뒤 실행되는 즉, call back 되는 함수를 말한다.
함수의 parameter(매개변수)로 들어가서 실행되는 함수이다.
// Synchronous callback
function synchronousPrint(print) {
print();
}
synchronousPrint(() => console.log("hello"));
// hello를 출력하는 함수를 인자(argument)로 synchronous 함수에 전달 ==> 곧바로 synchronous 함수 실행(동기)
// Asynchronous callback
function asynchronousPrint(print, timeout) {
setTimeout(print, timeout);
}
asynchronosPrint(() => console.log("hello"), 2000);
// hello를 출력하는 함수를 인자(argument)로 asynchronous 함수에 전달 ==> 2초 후 asynchronous 함수 실행(비동기)
callback hell(콜백지옥)의 문제점
1. 가독성이 떨어진다. => 어디서 어떻게 연결되어지는지 한눈에 알아보기 힘들다.
2. 디버깅, 에러 분석, 유지/보수가 어렵다. 1번 이유때문.
3. Promise
Promise란?
JavaScript에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 object(객체)
네트워크 통신을 하거나, 파일을 읽어오는 행위 등 시간이 걸리는 일들은 promise를 만들어서 비동기적(병렬로 처리한다고 이해하면 쉬울 것 같다.)으로 처리하는 것이 좋다.
Promise의 state는 뭘까?
promise의 state란 promise의 처리 과정을 의미한다.
기능이 수행 중인지(pending), 완료가 되어서 성공했는지(fulfilled), 실패했는지(rejected)를 나타내는 상태
Producer, Consumers란?
// 1. Producer(원하는 기능을 수행해서 해당하는 데이터를 만들어낸다.)
// Promise가 생성되면 자동적으로 실행된다.
const promise = new Promise((resolve, reject) => {
console.log("doing somthing...");
setTimeout(() => {
resolve("Hello, World!"); // 콜백 함수의 인자 resolve를 호출하면 이행(fulfilled) 상태가 된다.
// reject(new Error("no network")); // reject를 호출하면 실패(rejected) 상태가 된다.
}, 2000);
});
// 2. Consumers(원하는 데이터를 소비한다): then, catch, finally
promise
.then((value) => {
console.log(value);
}) // 이행(fulfilled) 상태가 되면 then()을 이용하여 처리 결과 값을 받는다.
.catch((error) => {
console.log(error);
}) // 실패(rejected) 상태가 되면 catch()를 이용하여 처리 결과 값을 받는다.
.finally(() => {
console.log("Done");
}); // promise 이행/실패에 상관없이 마지막에 호출되어진다.
Promise chaining이란?
여러 개의 promise를 연결하는 것.
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
}); // 이행 상태가 되어 1초뒤 resolve에 값이 들어온다.
fetchNumber
.then((num) => num * 2) // num parameter(매개변수)에 1이 들어오고 해당 콜백함수를 수행한 결과 값 2를 리턴한다.
.then((num) => num * 3) // num parameter(매개변수)에 2가 들어오고 해당 콜백함수를 수행한 결과 값 6을 리턴한다.
.then((num) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(num - 1), 1000);
});
}) // 새로운 promise를 만들어 1초 뒤 resolve에 5가 들어온다.
.then((num) => console.log(num)); // num parameter(매개변수)에 5가 들어오고 해당 콜백함수를 수행한다.
4. async/await
async와 await 이란?
깔끔하게 promise를 사용할 수 있는 방법
Syntactic sugar
기능은 동일하지만 더 간결한 코드를 사용함으로써 직관성/편의성을 높여주는 프로그래밍 문법
ex) 삼항 연산자(Conditional (ternary) operator), 애로우 함수(arrow function), async 등등
async 사용 방법
// 기존 promise 사용 방법
function fetchUser() {
return new Promise((resolve, reject) => {
// do network request in 10 secs...
resolve("ellie");
});
}
// async 사용 방법(async = Syntactic sugar), 함수 앞에 async라는 키워드만 넣어주면 끝.
async function fetchUser() {
// do network request in 10 secs...
return "ellie";
}
const user = fetchUser();
user.then(console.log);
console.log(user);
await 사용 방법
// delay를 주는 promise 생성
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// 1초뒤 🍎를 받아오는 promise를 async 기능을 사용해서 생성
async function getApple() { // await 키워드는 async가 붙은 함수 안에서만 사용 가능
await delay(1000);
return "🍎";
}
// 1초뒤 🍌를 받아오는 promise를 async 기능을 사용해서 생성
async function getBanana() {
await delay(1000);
return "🍌";
}
// 기존 promise를 사용해서 결과 값을 받아오는 방법(callback 지옥과 다를바 없다.)
function pickFruits() {
return getApple().then((apple) => {
return getBanana().then((banana) => `${apple} + ${banana}`);
});
}
// await를 사용해서 결과 값을 받아오는 방법
async function pickFruits() {
// JavaScript 특성상 동기적으로 🍎, 🍌의 promise를 실행할텐데,(2초가 걸리겠지)
// 둘 사이의 연관성이 없다면 굳이 동기적으로 받아올 필요가 없다.
// 따라서 Promise가 생성되면 자동적으로 실행되는 특징을 이용해서 변수(아래 두줄)를 생성해준다.
// 두 함수가 동시에 실행되므로 시간 단축!(1초!)
const applePromise = getApple();
const bananaPromise = getBanana();
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`;
}
// 유용한 promise API: Promise.all(모든 promise들이 병렬적으로 작동해서 배열에 전달된다.)
function pickAllFruits() {
return Promise.all([getApple(), getBanana()]) //
.then((fruits) => fruits.join(" + "));
}
// 유용한 promise API: Promise.race(배열에 가장 먼저 전달된 promise 값을 받아온다.)
function pickOnlyOne() {
return Promise.race([getApple(), getBanana()]);
}
참고 자료
드림 코딩 유튜브 - 자바스크립트 11~13
Evans Library
[번역] JavaScript: 도대체 콜백이 뭔데?