바쁜 음식점에 예약하지 않고 방문한 경우를 생각하자.
이런 상황에서 콜백은 내게서 전화번호를 받고 자리가 나면 알려주는 음식점과 비슷하다.
프라미스는 내가 진동벨을 받고 자리가 나면 알려주는 음식점과 비슷하다.
기다리는 동안 나와 음식점은 서로의 일을 할 수 있고, 어느 쪽도 서로 가만히 기다리지 않는다.
function work() {
const start = Date.now();
for (let i = 0; i < 1000000000; i++) {}
const end = Date.now();
console.log(end - start + 'ms');
}
console.log('작업 시작!');
work();
console.log('다음 작업');
/*
작업 시작!
516ms
다음 작업
*/
function work() {
setTimeout(() => {
const start = Date.now();
for (let i = 0; i < 1000000000; i++) {}
const end = Date.now();
console.log(end - start + 'ms');
}, 0);
// 0ms 후에 실행되게 설정되지만,
// 사실 4ms이후에 실행됨. (브라우저에서지정한 최소시간)
}
console.log('작업 시작!');
work();
console.log('다음 작업');
/*
작업 시작!
다음 작업
511ms
*/
function work(callback) {
setTimeout(() => {
const start = Date.now();
for (let i = 0; i < 1000000000; i++) {}
const end = Date.now();
console.log(end - start + 'ms');
callback(end - start);
}, 0);
}
console.log('작업 시작!');
work((ms) => {
console.log('작업이 끝났어요!');
console.log(ms + 'ms 걸렸다고 해요.');
});
console.log('다음 작업');
/*
작업 시작!
다음 작업
519ms
작업이 끝났어요!
519ms 걸렸다고 해요.
*/
setTimeout()
, setInterval()
, clearInterval()
은 모두 전역 객체(브라우저에서는 window
, 노드에서는 global
)에 정의되어 있음.콜백 함수는 프로그래밍에서 널리 사용되며, 비동기 프로그래밍 및 배열 내장 메서드에서 모두 사용될 수 있습니다. 그러나 이 두 맥락에서 콜백 함수의 역할과 동작 방식은 약간 다를 수 있습니다.
setTimeout()
setTimeout()
은 콜백의 실행을 지정된 ms(미리세컨드)만큼 지연하는 내장 함수.console.log('timeout 실행 전: ' + new Date());
function f() {
console.log('timeout 실행 후: ' + new Date());
}
setTimeout(f, 60 * 1000); // 1분
console.log('timeout 뒤에 실행');
console.log('timeout 뒤에 실행2');
/* 비동기적 실행의 결과.
timeout 실행 전: Mon Dec 26 2022 21:27:10 GMT+0900 (대한민국 표준시)
timeout 뒤에 실행
timeout 뒤에 실행2
timeout 실행 후: Mon Dec 26 2022 21:28:10 GMT+0900 (대한민국 표준시)
*/
/*
JS는 싱글 스레드를 사용하므로 만약 위의 예시에서 동기적으로 실행 하였다면,
60초 동안 컴퓨터가 대기 한 후 코드를 실행함.
그 과정에서 60초 동안 프로그램이 멈추고,
사용자의 입력을 받아들이지 않고,
화면도 업데이트하지 않음.
비동기적 테크닉은 이러한 일을 방지함.
*/
console.log('timeout 실행 전: ' + new Date());
setTimeout(function () {
console.log('timeout 실행 후: ' + new Date());
}, 60 * 1000); // 1분
console.log('timeout 뒤에 실행');
console.log('timeout 뒤에 실행2');
setInterval()
과 clearInterval()
setInterval()
은 콜백을 정해진 주기마다 호출하며 clearInterval()
을 사용할 때까지 멈추지 않음.// 분이 넘어가거나 10회째가 될 때까지 5초마다 콜백
const start = new Date();
let i = 0;
const intervalId = setInterval(() => {
let now = new Date();
if (now.getMinutes() !== start.getMinutes() || ++i > 10) {
return clearInterval(intervalId);
}
console.log(`${i}: ${now}`);
}, 5 * 1000);
/*
1: Mon Dec 26 2022 21:50:12 GMT+0900 (대한민국 표준시)
2: Mon Dec 26 2022 21:50:17 GMT+0900 (대한민국 표준시)
3: Mon Dec 26 2022 21:50:22 GMT+0900 (대한민국 표준시)
4: Mon Dec 26 2022 21:50:27 GMT+0900 (대한민국 표준시)
5: Mon Dec 26 2022 21:50:32 GMT+0900 (대한민국 표준시)
6: Mon Dec 26 2022 21:50:37 GMT+0900 (대한민국 표준시)
7: Mon Dec 26 2022 21:50:42 GMT+0900 (대한민국 표준시)
8: Mon Dec 26 2022 21:50:47 GMT+0900 (대한민국 표준시)
9: Mon Dec 26 2022 21:50:52 GMT+0900 (대한민국 표준시)
10: Mon Dec 26 2022 21:50:57 GMT+0900 (대한민국 표준시)
*/
function countdown() {
let i;
console.log('Countdown');
for (i = 5; i >= 0; i--) {
setTimeout(() => {
console.log(i === 0 ? 'GO!' : i);
}, (5 - i) * 1000);
}
}
countdown();
/*
Countdown
-1
-1
-1
-1
-1
-1
*/
(5 - i) * 1000
의 부분은 정상작동 하지만,setTimeout
에 전달된 함수는 비동기적으로 실행 되기 때문에 정상 작동하지 않음. (setTimeout
은 동기적으로 작동함)i
를 for
루프에서 선언하는 방식으로 해결 가능.function countdown() {
console.log('Countdown');
for (let i = 5; i >= 0; i--) {
setTimeout(() => {
console.log(i === 0 ? 'GO!' : i);
}, (5 - i) * 1000);
}
}
countdown();
/*
Countdown
5
4
3
2
1
GO!
*/
function increaseAndPrint(n, callback) {
setTimeout(() => {
const increased = n + 1;
console.log(increased);
if (callback) {
callback(increased);
}
}, 1000); // 1초에 한번씩 작동.
}
increaseAndPrint(0, (n) => {
increaseAndPrint(n, (n) => {
increaseAndPrint(n, (n) => {
increaseAndPrint(n, (n) => {
increaseAndPrint(n, (n) => {
console.log('작업 끝!');
});
});
});
});
});
function countdown(seconds) {
return new Promise(function (resolve, reject) {
for (let i = seconds; i >= 0; i--) {
setTimeout(function () {
if (i > 0) console.log(i + '...');
else resolve(console.log('GO!'));
}, (seconds - i) * 1000);
}
});
}
Promise
인스턴스를 반환.fullfilled
), 실패(rejected
) 두가지 상태를 가짐.settled
)을 의미. (단 한번만 일어남)Promise
생성자를 사용해 프로미스 생성.const myPromise = new Promise((resolve, reject) => {
// 비동기 작업을 수행하고
// 성공하면 resolve 호출, 실패하면 reject 호출
});
then
메서드catch
메서드로 오류를 처리.// 초를 매개변수로 받아 카운트다운히고,
// 끝나면 프라미스를 반환.
function countdown(seconds) {
return new Promise(function (resolve, reject) {
for (let i = seconds; i >= 0; i--) {
setTimeout(() => {
if (i === 13) return reject(new Error('Oh my god'));
if (i > 0) console.log(i + '...');
else resolve(console.log('GO!'));
}, (seconds - i) * 1000);
}
});
}
// then 핸들러는 성공 콜백과 에러 콜백을 받음.
countdown(5).then(
function () {
console.log('카운트다운 성공');
},
function (err) {
console.log('카운트다운 실패: ' + err.message);
}
);
catch
메서드// 초를 매개변수로 받아 카운트다운히고,
// 끝나면 프라미스를 반환.
function countdown(seconds) {
return new Promise(function (resolve, reject) {
for (let i = seconds; i >= 0; i--) {
setTimeout(() => {
if (i === 13) return reject(new Error('Oh my god'));
if (i > 0) console.log(i + '...');
else resolve(console.log('GO!'));
}, (seconds - i) * 1000);
}
});
}
const p = countdown(15);
// then 핸들러는 성공 콜백을 받고,
p.then(function () {
console.log('카운트다운 성공');
});
// catch 핸들러는 실패를 받음.
p.catch(function (err) {
console.log('카운트다운 실패: ' + err.massage);
});
myPromise
.then((result) => {
// 성공한 경우
})
.catch((error) => {
// 실패한 경우
});