- 싱글 스레드를 사용하는 언어
- 실행 순서가 순차적으로 진행된다.
function a() {
console.log("a");
}
function b() {
console.log("b");
}
a();
b(); // a 다음에 b가 출력된다.
작업이 완료될 때까지 기다리지 않고, 오래 실행되는 작업을 시작하여 해당 작업이 실행되는 동안에도 다른 이벤트에 응답할 수 있게 하는 기술입니다. (동시성)
💡 일반적인 자바스크립트 엔진은 싱글 스레드로 하나의 Call stack
을 가지는데, 어떻게 비동기적으로 실행될까?
➡️ JavaScript는 웹 브라우저에 탑재된 JavaScript 엔진
을 통해 비동기 작업을 처리한다.
이 과정에서 Web API
, Task Queue
, 그리고 Event Loop
가 각각 중요한 역할을 한다.
Call stack
에 마지막에 들어간 실행 컨텍스트가 먼저 실행된다. (FILO)
Call stack
에서 위에 쌓인 실행 컨텍스트가 제거될 때까지 아래 컨텍스트는 실행되지 않는다.
function fetchServer() {
setTimeout(() => {
console.log("time out");
}, 1000);
}
function drawUI() {
console.log("drawUI");
}
fetchServer();
drawUI();
console.log("콜스택 끝");
/* 결과:
drawUI
콜스택 끝
time out
*/
💡 Call stack
규칙대로라면 실행 순서는 time out -> drawUI -> 콜스택 끝이어야 한다. 그런데 어떻게 된 걸까?
➡️ 비동기 함수 setTimeout()
때문에 비동기 작업이 일어난 것이다.
fetchServer 실행 컨텍스트
가 콜스택
에 들어감
fetchServer 실행 컨텍스트
에 웹 API
용 기능이 포함되어 있음을 자바스크립트 엔진
이 발견하고 웹 브라우저 자바스크립트 엔진
에 이 기능을 맡김
3-1은 동시에 진행
3-1. 콜스택
에서 fetchServer 실행 컨텍스트
가 제거됨
3-1. 웹 브라우저 자바스크립트 엔진
은 맡겨진 작업을 진행 (모든 비동기 함수가 동시에 실행)
3-2. 작업이 끝나면 fetchServer
의 callback
을 Task Queue
에 넣어둠 (작업이 끝나는 순서대로 들어감)
콜스택
에 다음 실행 컨텍스트
가 들어가고 실행 후 제거됨
콜스택
이 텅텅 빔
웹 브라우저의 Task Queue
에 할 작업이 있는지 확인
있다면 등록 순서로 실행한다 (FIFO)
아래 그림은 소스와는 다르지만 비동기 작업 과정을 잘 보여준다. 출처
➡️ 그림을 통해 Call stack 실행 컨텍스트와 Web API가 동시에 실행되고, Call stack이 비워진 후 Task Queue 작업들이 Call stack에서 실행되는 것을 볼 수 있다.
비동기 함수가 여러 곳에 분포되어 있다면 어떻게 될까? 함수 실행 순서가 꼬여서 관리가 힘들 것이다. 이를 관리하기 위한 방법을 배워보자.
callback: 함수의 매개변수로 전달되는 함수
// 비동기적으로 실행되는 상태에서 활용되는 콜백 함수
// 원하는 순서: a -> b
// b()를 매개변수로 전달한다 (= callback 함수)
function a(callback) {
setTimeout(() => {
console.log("a");
callback(); // b()가 실행된다.
}, 0);
}
function b() {
console.log("b");
}
a(b);
// 위의 과정을 아래처럼 축약할 수 있다.
a(function b() {
console.log("b");
});
// 화살표 함수로도 나타낼 수 있다.
// 이는 화살표 함수의 탄생 배경이다 => 더 직관적인 callback 함수 표현을 위해
a(() => {
console.log("b");
});
➡️ callback
함수를 사용하면 내가 원하는 순서로 비동기 함수를 제어할 수 있다.
function task1(callback) {
setTimeout(() => {
console.log("task1");
callback();
});
}
function task2(callback) {
console.log("task2");
callback();
}
function task3(callback) {
console.log("task3");
callback();
}
function task4(callback) {
console.log("task4");
callback();
}
// 콜백 지옥
task1(() => {
task2(() => {
task3(() => {
task4(() => {
console.log("done");
});
});
});
});
➡️ 이곳저곳 흩뿌려져 있는 비동기를 callback
으로 관리하기가 어렵다. 할 수는 있지만 콜백 지옥에 빠질 수 있다.
콜백 지옥을 경험하고 생겨난
Promise
패턴, 이 또한 비동기 함수 호출을 제어할 수 있는 패턴 중 하나이다.
: 비동기 작업을 실행하는 코드를 의미
➡️ 비동기 작업 실행 코드의 상태
pending
⇒ 코드가 실행되었으나 응답은 오지 않은 상태fulfilled
⇒ 코드가 성공적으로 실행된 상태rejected
⇒ 코드가 실패한 상태요약된 그림은 아래와 같다. 출처
// 자바스크립트의 실행과 동시에 실행된다.
const promise = new Promise(function (resolve, reject) {
console.log("Promise");
resolve(); // 성공적으로 끝났음을 알림
reject(); // 거절 알림
});
: Promise의 결과를 소비하는 코드를 의미
➡️ Promise 인스턴스의 메서드
then
=> Promise를 리턴하고 두 개의 콜백 함수를 인수로 받음catch
=> 프로미스가 거부될 때 호출될 함수를 예약finally
=> 프로미스를 처리한 후 호출할 함수를 예약// Promise 객체를 생성한다. 성공(resolve) / 실패(reject)
const promise = new Promise(function (resolve, reject) {
console.log("Promise");
resolve(); // 성공적으로 끝났음을 알림
reject(new Error("failed")); // 실패로 에러 메시지(failed)를 보냄
});
// Promise 객체를 리턴한다.
promise
.then((v) => {
console.log(v);
})
// 오류를 호출한다. (failed)
.catch((err) => {
console.error(err); // 에러를 호출
});
then 메서드는 Promise를 리턴하기 때문에, 이어지는 then 호출들을 손쉽게 연결할 수 있다.
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
});
fetchNumber
// resolve의 전달값이 전달됨, Promise 값 반환
// fetchNumber의 resolve 반환값 2가 then에 매개변수로 오고 그 값을 반환한다.
.then((num) => num) // 2
// then의 반환값 2가 then에 매개변수로 오고 그 값에 2를 곱하여 반환한다.
.then((num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * 2); // 4
});
});
})
// then의 반환값 4가 then에 매개변수로 오고 그 값을 출력한다.
.then((num) => {
console.log(num); //
4
});
/*
결과: 4
*/
➡️ then()
은 유의미한 값을 반환하면 resolve
를 반환하는 것과 같다.
➡️ then()
은 명시적으로 Promise
객체를 반환해주는 것과 같다.
➡️ then()
체이닝 중 에러를 만나면 중단되고 catch
가 실행된다.
- Promise 객체를 만들고 이를 반환한다 (
resolve
또는reject
)- 그럼 이 반환값으로
then()
을 체인으로 사용할 수 있다.- 반환값이
then()
에 매개변수로 사용되고, 계속 유의미한 값을 넣으면then
이 연속된다.
➡️ .then
, .catch
, .finally
가 프라미스를 반환하면 나머지 체인은 프라미스가 처리될 때까지 대기합니다. 처리가 완료되면 프라미스의 result(값 또는 에러)가 다음 체인으로 전달된다.
Promise는 진짜 생애 최초로 배우는 데 callback을 겪어보니 왜 만들어졌는지 알게 되었고 탄생 배경과 promise를 배우니 자바스크립트의 원리를 이해하는 데 도움이 되었다.