비동기 개념
웹 브라우저?
: 서버에서 받아온 HTML, CSS, JS를 실행시켜주는 프로그램이다.
브라우저는 자바스크립트를 실행하는 과정이 있다.
브라우저는 C++이라는 언어로 코드가 짜져있는데, 브라우저는 실행해야할 자바스크립트 코드를 발견하면
C++ 언어로 만들어둔 stack에 넣어서 돌린다.
stack은 다 집어넣고 맨 윗줄부터 하나하나 실행시키는 공간이다.
하지만, 서버로의 ajax 요청, eventlistener, setTImeout 이런 코드들은 코드를 처리하기까지 시간이 오래걸린다.
그래서 이런코드들은 stack에 쌓아서 실행하지않고, Queue라는 곳에 집어넣고 기다렸다가
stack이 비어있을 때 차례로 Queue에서 stack으로 집어넣어서 실행해준다.
따라서, stack을 바쁘게 만들면 setTimeout 이런 코드들은 실행이 불가능하다.
🤷🏻♀️ 시간이 오래 걸리는 작업을 꼭 해야한다면 ?
1. setTimeout을 이용한다.
작업을 0초마다 쪼개서 실행하게 한다. 그러면 0초마다 Queue로 보내기때문에 그 사이사이 사용자의
eventlistener이런 코드가 실행가능하게된다. ( 0초로 실행해도 4ms로 동작한다. 최소시간이 4ms )
2. web worker를 이용한다.
다른 js파일을 이용해서 오래걸리는 작업을 따로 작동시키고 그게 완료되면 값을 가져오라고 명령한다.
(메인 js 파일)
var myWorker = new Worker('worker.js');
w.onmessage = function(e){
console.log(e.data) //이러면 1 나올듯
};
(worker.js 파일)
var i = 0;
postMessage(i + 1); //postMessage라는 특별한 함수가 있음
이런식으로 셋팅하면 worker.js에서 작업완료 시 postMessage() 이렇게 실행하면 다른 파일로 완료된 결과값을 전달해줄 수 있다.
이러면 stack이 바빠지지않는다.
🤷🏻♀️ 동기식 처리?
: 동기식 처리는 한번에 코드 한줄씩 차례로 실행되는 것이다.
위에서 설명된 웹브라우저의 stack이라는 코드실행공간에서 자바스크립트는 동기적으로 처리된다.
but, 비동기처리 (asynchronous)가되는 특수한 함수들 때문에 가끔 비동기적 실행이 된다.
console.log(1);
setTimeout(()=>{ console.log(2) }, 1000 };
console.log(3);
//출력
1
3
2
setTimeout()이라는 함수는 Queue로 들어가서 제쳐두고, 다른 코드부터 실행되므로 1-3-2순서로 출력된다.
이런 처리방식을 비동기라고 한다. ( 오래걸리는 작업이 있으면 제껴두고 다른거부터 처리 )
자바스크립트 언어자체는 동기식 처리가 되지만,
Web API와 연관된 특수한 함수들을 쓰면 작업이 오래걸리 때 다른것부터 실행이 가능하다.
( Web API에 오래걸리는 작업들을 넣어놓고 빨리 처리되는 함수들 먼저 실행한다. )

Callback함수
🤷🏻♀️ 비동기상황에서 순차적으로 코드를 실행하고 싶다면?
: 콜백함수를 활용한다.
콜백함수는 간단하게 함수안에 들어가는 함수를 전부 콜백함수라 생각할 수 있다.
function 첫째함수 () {}
function 둘째함수 () {}
//순서대로 실행하고 싶을때 setTimeout같은 함수가 있으면 이런식으로 실행시 실패한다.
첫째함수()
둘째함수()
//이럴때 콜백함수로 실행하면 순차적으로 실행이 가능하다.
첫째함수(둘째함수)
callback 함수
function 첫째함수(둘째) {
console.log(1);
둘째();
}
function 둘째함수(){
console.log(2);
}
첫째함수(둘째함수);
callback 함수의 문제점
- 순차적으로 실행하고싶은 코드가 여러개라면 코드가 지저분해진다.
이러한 문제점을 개선한 것이 promise.
첫째함수(function(){
둘째함수(function() {
셋째함수(function() {
})
})
});
Promise
Promise
: 자바스크립트의 새로운 기능이라기보다는 코드/함수 디자인 패턴이다.
(동기를 비동기로 만들어주는 코드가 아니고, 디자인 패턴)
let 프로미스 = new Promise(성공, 실패) {
let 어려운 연산 = 1+1;
//연산 후 실행하고 싶은 코드를 적는다. 성공() 적으면 .then에 있는, 실패() 적으면 .catch
//성공()
//실패()
};
프로미스
.then(()=> {}) //성공시 실행되는 코드
.catch(()=> {}) //실패시 실행되는 코드
new Promise()로 생성된 변수를 콘솔창에 출력해보면 현재상태를 알 수 있다.
[ 3가지 상태 ]
성공/실패 판정 전에는 pending
성공 후에는 resolved
실패 후에는 rejected
프로미스안에 콜백함수 실행
성공 했을 때 then() 함수내의 코드를 실행
실패했을 경우에는 catch( ) 함수내의 코드를 실행한다. (callback함수와 다른점)
콜백대신 쓰는이유?
- 콜백보다 코드가 깔끔하다.
- 성공/실패의 경우 각각 다른 코드가 실행가능하다.
[예시]
> setTimeout 예시
1초후에 성공하는 promise - 성공시 특정코드 실행
let 프로미스 = new Promise(function (성공, 실패) {
setTimeout(() => {
성공();
}, 1000);
});
프로미스
.then(() => {
console.log("success");
})
.catch(() => {
console.log("fail");
});
async / await
async를 사용하면 promise object가 저절로 생긴다.
( async는 함수 앞에만 붙일 수 있다. )
async function 더하기(){
return 1 + 1
}
//async를 붙였으니까 then 사용 가능
//return 결과값 then 함수에서 파라미터로 넣어주면 사용가능
더하기().then(function(결과){
console.log(결과)
});
then 대신 await를 async function 안에서 쓸 수 있다.
await를 붙여주는 함수가 다 끝날 때 까지 기다려준다.
async function 더하기() {
let 연산 = new Promise((성공, 실패) => {
let 결과 = 1 + 1;
성공(100);
});
let 결과 = await 연산 //연산 promise를 기다린다음에 완료되면 결과를 변수에 담아라.
console.log(결과)
//아래의 promise코드와 위의 await는 같은 역할을 한다.
//promise.then (()=> {
//console.log('결과')
}
더하기()
await는 실패하면 에러가 나고 코드가 멈춘다.
promise가 실패할 경우 코드실행을 멈추고 싶지 않으면 try catch를 사용한다.
try{}안의 코드가 에러나고 멈출경우, 대신 catch{}내부의 코드를 실행한다.
try { 이걸해보고 에러나면 } catch { 이걸 실행해 }
async function 연산 () {
let 프로미스 = new Promise ((성공, 실패)=> {
let 연산 = 1+1;
성공(연산)
});
try {
let 결과 = await 프로미스;
console.log(결과);
} catch {
console.log('실패')
}
}
연습 문제 (비동기, promise, async/await)
[기초 문제]
문제 1 — Promise 기본 생성
/**
* 1초 뒤에 "done"을 반환하는 Promise를 생성하는 함수를 작성하세요.
*
* 예시:
* await delay(); // 👉 "done" (1초 뒤)
*/
function delay(): Promise<string> {
// TODO
}
🔹 힌트: setTimeout을 Promise로 감싸기 (new Promise(resolve => ...))
const delay = (): Promise<string> => {
return new Promise((resolve) =>
setTimeout(() => {
resolve("done");
console.log("done");
}, 1000)
);
};
⚙️ 문제 2 — then 체이닝
/**
* 아래 Promise 체인을 완성하세요.
* 1초 뒤 "A"를 반환한 다음, 그 결과에 "B"를 이어붙여 최종 결과는 "AB"가 되게 하세요.
*/
const result = new Promise((resolve) => {
// TODO
})
// TODO: then 체인으로 "B"를 붙이기
console.log(await result); // 👉 "AB"
🔹 힌트: .then()은 이전 resolve 결과를 다음 단계로 넘긴다.
const result = new Promise((resolve) => {
setTimeout(() => resolve("A"), 1000);
}).then((res) => {
return res + "B";
});
💣 문제 3 — 에러 처리
/**
* Promise가 실패하면("Error!") catch 블록에서 "Recovered"를 반환하게 하세요.
*/
const result = new Promise((_, reject) => {
// TODO: "Error!"로 reject
})
// TODO: .catch()로 에러를 잡고 "Recovered" 반환
console.log(await result); // 👉 "Recovered"
🔹 힌트: .catch()도 Promise를 반환한다. 내부에서 return하면 “복구”됨.
const result = new Promise((_, reject) => {
reject("Error!"); // ❌ 실패 신호를 보냄
})
.catch((err) => {
console.log("에러 잡힘:", err);
return "Recovered"; // ✅ 에러 대신 복구된 값 반환
});
console.log(await result); // 👉 "Recovered"
🔄 문제 4 — async/await 변환
/**
* then/catch 체인으로 작성된 아래 코드를 async/await 문법으로 변환하세요.
*/
new Promise((resolve) => setTimeout(() => resolve("OK"), 500))
.then((res) => console.log(res))
.catch((err) => console.error(err));
const solution = async () => {
try {
const data = await new Promise((res) => setTimeout(() => res("OK"), 500));
console.log(data);
} catch (err) {
console.error(err);
}
};
🧠 문제 5 — 안전한 실행 (try-catch or then-catch)
/**
* Promise가 성공하면 [data, undefined],
* 실패하면 [undefined, error]를 반환하는 safePromise 함수를 작성하세요.
*
* 예시:
* await safePromise(Promise.resolve("A")); // 👉 ["A", undefined]
* await safePromise(Promise.reject("X")); // 👉 [undefined, "X"]
*/
async function safePromise<T>(promise: Promise<T>): Promise<[T?, any?]> {
// TODO
}
🔹 힌트: try/catch 또는 .then().catch() 둘 다 가능!
// try ~catch
async function safePromise<T>(promise: Promise<T>): Promise<[T?, any?]> {
try {
const data = await promise; // ✅ await로 결과를 기다림
return [data, undefined]; // 성공 시
} catch (error) {
return [undefined, error]; // 실패 시
}
}
// then ~catch
function safePromise<T>(promise: Promise<T>): Promise<[T?, any?]> {
return promise
.then((data) => [data, undefined] as [T, undefined])
.catch((error) => [undefined, error] as [undefined, any]);
}
[응용 문제]
🧩 문제 1 — 안전한 다중 요청 (Safe All)
/**
* 여러 Promise를 동시에 실행하고,
* 성공한 결과만 모아서 반환하세요.
* 실패한 Promise는 무시합니다.
*
* 예시:
* await safeAll([
* Promise.resolve(1),
* Promise.reject("fail"),
* Promise.resolve(3)
* ]);
* // 👉 [1, 3]
*/
async function safeAll(promises: Promise<any>[]): Promise<any[]> {
// TODO
}
🔹 힌트: Promise.allSettled() 또는 .catch() 체이닝으로 실패 무시
async function safeAll(promises: Promise<any>[]): Promise<any[]> {
const results = await Promise.allSettled(promises);
return results
.filter((r) => r.status === "fulfilled")
.map((r) => (r as PromiseFulfilledResult<any>).value);
}
⚙️ 문제 2 — 요청 타임아웃 처리
/**
* 주어진 Promise가 timeout(ms) 내에 완료되지 않으면 "timeout"을 반환하세요.
*
* 예시:
* await withTimeout(fetchData(), 1000);
* // fetchData()가 1초 이내에 완료되면 결과값 반환
* // 1초를 초과하면 "timeout" 반환
*/
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | "timeout"> {
// TODO
}
🔹 힌트: Promise.race()를 써야 한다. 하나는 원래 promise, 하나는 setTimeout으로 만들어진 “타이머 Promise”
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | "timeout"> {
const timeoutPromise = new Promise<"timeout">((resolve) =>
setTimeout(() => resolve("timeout"), ms)
);
return Promise.race([promise, timeoutPromise]);
}
⚡ 문제 3 — 순차적 비동기 실행 (Order 유지)
/**
* 비동기 작업 배열을 순서대로 실행하고,
* 각 결과를 순서대로 배열로 반환하세요.
*
* 예시:
* const tasks = [
* () => Promise.resolve("A"),
* () => new Promise(res => setTimeout(() => res("B"), 500)),
* () => Promise.resolve("C"),
* ];
* await runSequential(tasks);
* // 👉 ["A", "B", "C"]
*/
async function runSequential(tasks: (() => Promise<any>)[]): Promise<any[]> {
// TODO
}
🔹 힌트: for...of 문이나 reduce로 순서 보장
async function runSequential(tasks: (() => Promise<any>)[]): Promise<any[]> {
const results = [];
for (const task of tasks) {
const result = await task();
results.push(result);
}
return results;
}
🧠 문제 4 — 실패 시 재시도 로직
/**
* 주어진 비동기 함수를 최대 retries 번 재시도하세요.
* 모든 시도가 실패하면 마지막 에러를 throw하세요.
*
* 예시:
* let count = 0;
* await retry(async () => {
* count++;
* if (count < 3) throw "fail";
* return "success";
* }, 5);
* // 👉 "success"
*/
async function retry<T>(fn: () => Promise<T>, retries: number): Promise<T> {
// TODO
}
🔹 힌트: try/catch 안에서 재귀 호출 or while loop로 재시도
async function retry<T>(fn: () => Promise<T>, retries: number): Promise<T> {
let count = 0;
let lastError;
while (count < retries) {
try {
return await fn();
} catch (err) {
lastError = err;
count++;
}
}
throw lastError;
}
💣 Promise 기반 큐 구현
/**
* 한 번에 최대 N개만 동시에 Promise를 실행하는
* 병렬 제어 함수 runLimited를 구현하세요.
*
* 예시:
* const tasks = [
* () => delay(1000).then(() => 1),
* () => delay(500).then(() => 2),
* () => delay(200).then(() => 3),
* () => delay(100).then(() => 4),
* ];
* await runLimited(tasks, 2);
* // 👉 [1, 2, 3, 4]
*/
async function runLimited(tasks: (() => Promise<any>)[], limit: number): Promise<any[]> {
// TODO
}
🔹 힌트: Promise.race()로 “먼저 끝난 작업”을 감지하고 다음 작업을 채워 넣는다.
(file upload, API rate-limit에 자주 쓰임)
async function runLimited(tasks: (() => Promise<any>)[], limit: number): Promise<any[]> {
const results: any[] = []; // ✅ 최종 결과를 "원래 순서"대로 담을 배열
let i = 0; // ✅ 다음에 실행할 task의 인덱스 포인터
const running: Promise<void>[] = []; // ✅ 현재 실행 중인 작업(슬롯)들
// ✅ "슬롯 하나"를 담당하는 실행 함수: 끝나면 스스로 다음 작업을 이어받음
const runNext = async () => {
if (i >= tasks.length) return; // 1) 더 이상 실행할 작업이 없으면 종료
const index = i++; // 2) 지금 이 슬롯이 맡을 작업 인덱스 할당(그리고 포인터 1 증가)
try {
const value = await tasks[index](); // 3) 해당 작업 실행(함수를 호출해야 Promise가 생김)
results[index] = value; // 4) 결과를 "원래 인덱스" 자리에 저장(순서 보장)
} finally {
// 5) 이 슬롯이 방금 맡은 작업을 끝냈으니, 남은 작업이 있으면 "다음 작업"을 이어서 수행
await runNext();
}
};
// ✅ 최초에 limit(또는 tasks 길이) 만큼 "슬롯"을 켠다
for (let j = 0; j < Math.min(limit, tasks.length); j++) {
running.push(runNext()); // 각 슬롯은 runNext 루프를 통해 작업을 연속적으로 이어받음
}
// ✅ 모든 슬롯이 자기 몫의 작업을 다 끝낼 때까지 대기
await Promise.all(running);
return results; // ✅ 순서 보장된 최종 결과
}
'Archive' 카테고리의 다른 글
| [TS] declare / d.ts / index signatures (0) | 2022.01.13 |
|---|---|
| [TS] React (0) | 2022.01.12 |
| [GIT] Github 잔디 안심어지는 현상 (0) | 2022.01.05 |
| [JS] getter,setter / import,export (0) | 2022.01.05 |
| [TS] Private / Static / Generic 등 (0) | 2022.01.05 |
