비동기란 무엇일까? 이걸 알아보기 위해서는 우선 동기의 개념도 필요하다!
'동기' 는 하나의 작업이 실행되는 동안 다른 작업을 수행하지 않는 방식
=> 하나의 작업이 완료될 때까지는 다른 작업을 하지 않는 순차적 방식이다!
그렇다면 비동기라는 건 뭘까?
동기와는 반대되는 개념으로, 어떠한 작업이 종료되길 기다리지 않고 그 다음 작업을 병렬적으로 수행하는 방식이다.
자바스크립트는 싱글 스레드(Single Thread) 언어이지만, 비동기 처리 방식을 이용하여 마치 여러 작업들을 한 번에 수행하는 멀티 스레드(Multi Thread)처럼 보이게 한다!
동시에 여러 작업을 한 번에 처리할 수 있기 때문에 이렇게 훨씬 더 빠르게 작업을 수행할 수 있다는 장점이 있지만, 좀 더 와닿게 예시를 들자면!
웹페이지 접속 시 모든 화면이 순차적으로 로딩된다면 접속 시간이 엄청나게 길어질텐데... 비동기 처리를 하면 어느 하나가 로딩이 끝날 때까지 기다리는게 아니라, 먼저 로딩된 것부터 완료되어 나오기 때문에 전체적인 접속시간이 확 줄어든다.
비동기 처리를 할 수 있는 내장함수로, 매개변수는 콜백함수와 delayTime!
const workA = () => {
setTimeout(() => {
console.log('workA');
}, 5000);
};
const workB = () => {
setTimeout(() => {
console.log('workB');
}, 3000);
};
const workC = () => {
setTimeout(() => {
console.log('workC');
}, 10000);
};
const workD = () => {
console.log('workD');
};
workA();
workB();
workC();
workD();
비동기 처리를 하지 않았다면, 5초 뒤에 A, 3초 뒤에 B, 10초 뒤에 C, 그 다음 D가 나와 총 18초가 소요되었겠지만,setTimeout함수를 사용하여 비동기 처리를 했기 때문에
D가 제일 먼저 출력되고, 3초 뒤에 B, 그 다음 2초 뒤에 A, 5초 뒤에 C
그렇다면 전체 소요 시간은 10초이다!
프로미스 객체는 new를 통해 생성가능하고, 생성 시 인수로 executor라는 실행함수를 전달해야 한다.
executor는 프로미스 객체가 생성될 때 자동으로 실행되는 함수로, 매개변수로 resolve, reject라는 콜백함수를 전달해야 한다!

const executor = (resolve, reject) => {
setTimeout(() => {
resolve('성공');
reject('실패');
}, 3000);
};
const promise = new Promise(executor);
promise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
비동기 처리 방식 중 가장 큰 문제는 <콜백지옥> 인데, 프로미스 객체를 사용하면 해결할 수 있다.
작업들을 원하는 순서대로 처리하고, 비동기의 결과값을 또다른 비동기 함수에서 사용
const workA = (value, callback) => {
setTimeout(() => {
callback(value + 5);
}, 5000);
};
const workB = (value, callback) => {
setTimeout(() => {
callback(value - 3);
}, 3000);
};
const workC = (value, callback) => {
setTimeout(() => {
callback(value + 10);
}, 10000);
};
workA(10, (resA) => {
console.log(`workA : ${resA}`);
workB(resA, (resB) => {
console.log(`workB : ${resB}`);
workC(resB, (resC) => {
console.log(`workC : ${resC}`);
});
});
});
=> 가독성이 떨어지고 복잡하다! 콜백지옥에 빠졌다!
이번에는 함수 내부에 프로미스 객체를 생성하고, 생성된 객체를 반환해보자
const workA = (value) => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(value + 5);
}, 5000);
});
return promise;
};
const workB = (value) => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(value - 3);
}, 3000);
});
return promise;
};
const workC = (value) => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(value + 10);
}, 10000);
});
return promise;
};
workA(10)
.then((resA) => {
console.log(`1. ${resA}`);
return workB(resA);
})
.then((resB) => {
console.log(`2. ${resB}`);
return workC(resB);
})
.then((resC) => {
console.log(`3. ${resC}`);
})
.catch((error) => {
console.log(error);
});
코드가 더 길어졌을지는 모르겠지만 확실히 한 눈에 보기 편하다!
이렇게 계속해서 프로미스 객체를 반환해 then 메서드를 연속으로 사용하는 방식을 프로미스 체이닝(Promise Chaining) 이라고 부른다.
프로미스 객체를 좀 더 편리하게 사용할 수 있도록 도와준다!
우선 프로미스 객체를 반환하는 delay 라는 이름의 함수와 delay 함수를 호출하는 start 함수를 작성해보자
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('3초가 지났습니다.');
}, ms);
});
};
const start = () => {
delay(3000).then((res) => {
console.log(res);
});
};
start();
delay 함수 내부 <- 프로미스 객체를 반환하는 코드 작성 .
resolve에 '3초가 지났습니다.' 전달.
resolve에 전달된 값 출력.
비동기 작업을 포함하고 있기 때문에, 프로미스 객체를 반환하는 함수에 작성하는 키워드
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('3초가 지났습니다.');
}, ms);
});
};
const start = async () => {
delay(3000).then((res) => {
console.log(res);
});
};
start();
어떠한 함수 앞에 async를 붙이게 되면, 해당 함수는 자동으로 프로미스 객체 반환!
await은 async가 작성된 함수 내에서 사용할 수 있는 키워드!
await 키워드가 포함된 코드가 실행되면, 해당 작업이 종료될 때까지 프로그램 실행이 중단됨
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('3초가 지났습니다.');
}, ms);
});
};
const start = async () => {
let result = await delay(3000);
console.log(result);
};
start();
프로미스가 처리될 때까지 기다리면서, 그동안은 함수의 실행을 중단.
start 함수를 호출하면, delay 함수의 프로미스 객체의 처리가 완료될 때까지 잠시 중단되었다가 프로미스 객체의 처리가 완료되면 코드가 순서대로 다시 실행
then 과 비슷한 느낌이지만 훨씬 더 가독성 있고 편리하게 작성할 수 있다!
실행 순서 예측이 안 되었던 기존과는 달리 순서를 예측할 수 있게 됨!
await 키워드는 프로미스 객체를 반환하는 함수의 내부에서만 사용이 가능하기 때문에 async 키워드가 붙어있는 함수의 내부에서만 사용 가능하다는 것을 주의해서 사용해야 한다!
async/await 를 사용하기 전에는 then/catch를 사용하여 에러 핸들링을 하였지만 async/awiat을 사용할 때에는 에러 처리를 어떻게 해야할까?
try / catch
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('3초가 지났습니다.');
}, ms);
});
};
const start = async () => {
try {
let result = await delay(3000);
console.log(result);
} catch (error) {
console.log(error);
}
};
start();
const workA = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('workA');
}, 5000);
});
};
const workB = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('workB');
}, 3000);
});
};
const workC = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('workC');
}, 10000);
});
};
const start = async () => {
try {
let resultA = await workA();
let resultB = await workB();
let resultC = await workC();
console.log(resultA);
console.log(resultB);
console.log(resultC);
} catch (err) {
console.log(err);
}
};
start();
위에서 배운 모든 것을 총집합하여 작성해보았다.
start 함수에서 이렇게 다 await 처리를 하면...
생각해보니 5초 뒤 workA 실행 그 다음 3초 뒤에 workB실행, 10초 뒤 workC가 실행된다.
이렇게 되면 비동기 처리를 하는 의미가 전혀 없어진다!!!!!!!!!!!1
결국은 또 18초가 소요되게 된다
가독성 좋고 작성하기 쉬운 문법들을 사용하면서도, 비동기 처리를 제대로 하기 위해서는
각 작업을 병렬로 실행해 실행시간을 단축하는 Promise.all 사용!
const workA = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('workA');
}, 5000);
});
};
const workB = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('workB');
}, 3000);
});
};
const workC = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('workC');
}, 10000);
});
};
const start = async () => {
try {
let results = await Promise.all([workA(), workB(), workC()]);
results.forEach(result => console.log(result));
} catch (err) {
console.log(err);
}
};
start();
이러면 10초만에 A, B, C가 출력된다!
그치만 여기서 의문점이 생겼다...
병렬시작이면 우선 전부 다 실행?
실행 후 5초뒤에 A가 나오고,
동시에 실행되었으나 B는 3초가 걸리니까 완료된 상태로 대기하다가 A나오고 B나오고?
C는 5초 뒤에 나오는 건가?
결론은 실행 후 5초 뒤에 A,B 출력, 그 다음 5초 뒤에 C가 나오는 건가?!!
궁금해져서 chatGPT한테 물어봣따

Promise.all()은 세 작업 중 가장 오래 걸리는 workC()가 완료될 때까지 기다리므로, 10초 후에 세 개의 결과(workA, workB, workC)가 모두 출력됩니다.
따라서, 5초 후에 A 출력, 바로 B 출력, 그리고 5초 후에 C 출력이 되는 것이 아니라, 10초 후에 세 개의 결과가 동시에 출력됩니다.
이론으로는 쉬운 거 같은데 사용하기엔 익숙하지 않아서 후에 예제 문제를 많이 풀어봐야될 거 같다... ㅜ0ㅜ
지금까지 비동기 처리를 왜 배웠냐! 바로 웹 사이트에서 데이터를 주고받는 통신을 하기 위해서이다!!(다른 용도로도 많이 쓰이지만 우선 내 입장에서는 이렇다!)
웹 사이트 => 웹 브라우저에서 네트워크를 통해 데이터나 서비스를 제공하는 컴퓨터인 서버와 통신
브라우저는 클라이언트! 네트워크로 연결된 서버로부터 정보를 제공받는다!


API => 웹 브라우저와 같은 클라이언트와 서버 사이의 연결!
따라서 API를 호출하기 위해서는 가장 먼저 API 호출에 응답할 수 있는 서버 필요!
API는 API 주소(API URL)를 통해 호출할 수 있고, API 주소를 사용해 API를 호출하면, 서버는 화면에 표시된 데이터들을 웹 브라우저에게 전달한다.
데이터는 한 개의 배열로 이루어져있고, 배열 안에는 각각의 데이터가 객체 형태로 저장되어있다.
=> 이러한 자바스크립트의 데이터 구조를 JSON이라고 부른다.
JSON은 key와 value의 쌍!
let response = fetch('https://jsonplaceholder.typicode.com/users')
.then((res) => console.log(res))
.catch((err) => console.log(err));
console.log(response);
fetch 함수는 비동기 함수이기 때문에 가장 아래에 작성된 response를 출력하는 코드가 먼저 출력이 되고, 이후 프로미스에서 resolve 함수를 통해 전달된 값을 then 메서드를 사용해서 출력
이 Response 객체는 API 호출에 성공했을 때 반환되는 API 성공 객체로, 이렇게 fetch 메서드를 사용해 API를 호출하면 API 성공 객체 자체가 반환!!!!
따라서 우리가 원하는 JSON 형식으로 값을 반환하려면?
=> async / await 사용하자!
JSON 데이터를 활용하기 위해서는 JSON 문자열을 파싱해서 객체 형태로 반환!!
const getData = async () => {
let response = await fetch('https://jsonplaceholder.typicode.com/users');
let data = await response.json();
console.log(data);
};
getData();
데이터를 요청할 때에는 네트워크 오류 혹은 인터넷 속도 등의 다양한 이유로, 데이터 요청에 실패할 수 있다는 점을 주의! => 에러 처리 코드 필요!
const getData = async () => {
try {
let response = await fetch('https://jsonplaceholder.typicode.com/users');
let data = await response.json();
console.log(data);
} catch (err) {
console.log(err);
}
};
getData();
try문 : fetch 메서드를 사용해서 API를 호출하는 코드를 작성해주고, 이후 JSON 형태의 데이터를 알맞게 파싱해서 출력하는 코드를 작성
catch문 : 매개변수로 에러를 전달받아, 에러가 발생하면 에러 내용을 출력하는 코드를 작성
습관처럼 쓰고 있었던 async/await... 왜 쓰는지 어떤 로직으로 돌아가는지 제대로 개념 잡기 위해 다시 공부를 시작했는데 생각보다 어려운 점도 많고 이해하고 쓰려니 오히려 더 버벅이는 점도 많아서 다양한 예제나 구현을 많이 해봐야겠다는 생각을 했다.
js에서의 기본 중의 기본! 제일 중요한 부분인데 왜이렇게 대충하고 넘어갔을까 공부를 하면 할 수록 후회가 된다...
이 포스팅은 '한 번에 끝내는 자바스크립트' 강의를 보고 작성하였는데, 후반부쪽에 중간프로젝트나 다른 토이 프로젝트들도 많이 있는 거 같아서 열심히 개념 공부를 하고 실제로 구현해보며 손에 익히는 연습을 해야될 거 같다!!
아자아자 파이팅이댜...
+++ 포켓몬으로 예제 해보기!!!!!
아래의 API 주소는 포켓몬들의 정보가 담겨있는 data를 반환합니다. 주어진 API 주소를 사용해, 아래의 조건을 모두 만족하는 코드를 작성하세요.
출력 결괏값 : (이상해씨, 이상해풀, 이상해꽃, 캐터피, 단데기, 모다피, 우츠동, 우츠보트, 스라크의 정보가 담긴 9개의 객체들이 모인 배열)
const API_URL = 'https://pokemon-api-ecru-eta.vercel.app;
답 작성하기~.~
const API_URL = 'https://pokemon-api-ecru-eta.vercel.app';
async function getPokemonData(){
try{
let response = await fetch(API_URL); // api 호출을 통해 데이터 가져오기
let data = await response.json(); // 데이터 값을 json 형태로
let pokemonArray = data.data; // 배열 형태로
const answer = pokemonArray.filter((elm)=> elm.color === 'green');
console.log(answer);
}
catch(error){
console.log('error');
}
}
getPokemonData();
간단한 예제 같았지만 조금 버벅인 부분이 있었다.
json 형태로 파싱만 한 다음에 분류하여 출력하려고 해서 오류가 났는데!
우선 제공하는 데이터를 확인해보면

이런식으로 되어있는 걸 볼 수 있다!!!!
json 형태로 파싱했을 시에는 저 객체 형태의 데이터가 다 오는 거니까
객체 안에 있은 data를 배열 형태로 꺼내와서 filter 처리를 해야되는 것이었다!!!!
response.json()으로 변환한 것은 JSON 형태의 전체 응답 객체!
우리가 필요한 배열을 꺼내와야 한다!

출력 완료! ~.~