function str(){
console.log('I \'m string');
}
console.log(1);
str();
console.log(2);
console.log(3);
본래 코드는 위에서 아래 순서로 실행된다.
함수 str은 코드 가장 위에 정의되어 있지만 실행되지 않고 있다가
1이 출력된 이후 호출되어 문자열을 뱉어낸다.
1
I'm string
2
3
다음 예제도 살펴보자
function b() {
console.log("b called!");
}
function a(another) {
console.log("a started!");
another();
console.log("a ended!");
}
console.log(1);
a(b);
console.log(3);
1
a started!
b called!
a ended!
3
a함수는 another 인자를 받아서 함수처럼 호출하고 있다
이처럼 a함수를 호출할 때 b함수를 전달하는 것을 '콜백함수를 전달한다' 고 한다.
종합해보자면 함수는
1. 선언과 호출의 시점을 다르게 해서 코드의 실행 흐름을 조작하고
2. 인자로 함수를 받아 실행 할 수 있다
let i = 0;
function decryptData(callback, wait, age) {
let start = new Date().getTime();
while (new Date().getTime() < start + wait);
callback(`${age}세 은행원`);
}
fetchData(console.log, 1000, 20);
fetchData(console.log, 1000, 37);
fetchData(console.log, 1000, 59);
자 이제부터 익명의 직원 3명의 개인정보를 가져오는 작업을 한다고 가정해보자
정보는 암호로 되어있어 하나의 정보를 해독 후에 가져오는데 시간 1초가 꼭 걸린다고 한다면
한명의 직원 정보를 가져오는 동안 다른 직원의 정보를 동시에 가져오는 일은 할 수 없다. 즉 3초를 꽉 채워 기다려야 한다는 뜻이다.
하지만 해독을 다른 누군가가 대신 해주고 나는 답변을 기다리기만 한다면,
또 3대의 컴퓨터로 3명의 정보를 동시에 받아오면 어떨까?
function finishJob(num) {
console.log(`${num}번 요원의 정보를 받아왔습니다.`);
}
setTimeout(finishJob, 2000, 1);
setTimeout(finishJob, 1500, 2);
setTimeout(finishJob, 1000, 3);
console.log("정보 요청을 모두 보냈습니다.");
정보 요청을 모두 보냈습니다.
3번 요원의 정보를 받아왔습니다.
2번 요원의 정보를 받아왔습니다.
1번 요원의 정보를 받아왔습니다.
위 코드는 3명의 정보를 동시에 받아서 정해진 시간 뒤에 출력하고 있다.
그런데 결과값이 조금 이상하다.
마지막 console.log 함수 결과가 가장 먼저 출력되었다. 어떻게 된 일일까?
setTimeout(기다린 후 호출할 콜백함수, 기다릴 밀리초, 콜백함수에 넘길 인자(옵션))
setTimeout 은 기다리는 함수로 일정한 ms 뒤에 콜백함수를 호출한다.위를 아래와 같이 표현해보면 setTimeout이 비동기 방식임을 알 수 있다.
-> 예약이 끝나자마자 동시에 각자의 예약 시간을 기다리고, 이때 하나의 콜백함수가 다른 콜백함수의 기다림에 영향을 주지 않기 때문이다.
setTimeout(callback1, 1000ms, arg (옵션)) -> 함수 끝 , 예약됨
setTimeout(callback2, 1500ms, arg (옵션)) -> 함수 끝 , 예약됨
setTimeout(callback3, 2500ms, arg (옵션)) -> 함수 끝 , 예약됨
| | | |
'hi' 출력 | | |
callback1 | |
1000ms callback2 |
1500ms |
callback3
2500ms
위에서 본 것처럼 비동기 작업은 한번에 여러 작업을 동시에 수행할 수 있는 장점이 있지만, 의존성이 길게 이어져 있는 경우 일이 복잡해진다
왜냐하면 비동기 작업은 함수가 호출되는 시점에 시작되고 이때 다음 작업(콜백함수)도 넘겨줘야 하기 때문이다.
기본적인 형태부터 알아본 후 사용 예제까지 살펴보자.
const promise1 = new Promise((resolve,reject) => {
// 비동기 작업
})
executor 라고 한다.resolve, 다른 하나는 reject이다.비동기 작업은 언제 끝날 지 알 수 없기 때문에 성공, 실패에 따른 후작업까지
해 주어야 한다.
then 으로 성공 후 작업 연결catch 로 오류나 실패를 잡아냄 Promise 작업이 끝난 뒤의 후속작업을 처리하는 메소드이다.
then : 비동기 작업이 성공하면 실행할 동작 지정, 함수를 인자로 받음
catch : 비동기 작업이 실패하면 실행할 동작, 함수를 인자로 받음
const promise1 = new Promise((resolve, reject) => {
resolve(); // reject();
});
promise1
.then(() => {
console.log("then!");
})
.catch(() => {
console.log("catch!");
});
결과
then! //catch!
위에서 설명한 것처럼 resolve는 then으로 처리, reject는 catch로
처리되는 것을 볼 수 있다.
비동기 작업을 수행할 때마다 new Promise 를 이용해 객체를 만들기는 번거롭다.
이럴땐 new Promise 의 결과를 바로 리턴하는 함수를 만들어 재사용하면 된다.
function startAsync(age) {
return new Promise((resolve, reject) => {
if (age > 20) resolve();
else reject();
});
}
setTimeout(() => {
const promise1 = startAsync(25);
promise1
.then(() => {
console.log("1 then!");
})
.catch(() => {
console.log("1 catch!");
});
const promise2 = startAsync(15);
promise2
.then(() => {
console.log("2 then!");
})
.catch(() => {
console.log("2 catch!");
});
}, 1000);
new Promise 가 실행되어 비동기 작업이 시작된다.then 혹은 catch 로 후속 작업을 처리한다.결과
promise1 -> resolve ( ) -> then -> // 1 then!
promise2 -> reject ( ) -> catch -> // 2 catch!
위에서 startAsync를 호출하여 변수를 만들 때 인자를 전달했었다.
이 인자를 new Promise 에서 resolve, reject 일 때 각각 다르게
처리해 then, catch 에 전달할 수 있다.
function startAsync(age) {
return new Promise((resolve, reject) => {
if (age > 20) resolve(`${age} success`);
else reject(new Error(`${age} is not over 20`));
});
}
setTimeout(() => {
const promise1 = startAsync(25);
promise1
.then((value) => {
console.log(value);
})
.catch((error) => {
console.error(error);
});
const promise2 = startAsync(15);
promise2
.then((value) => {
console.log(value);
})
.catch((error) => {
console.error(error);
});
}, 1000);
결과
25 success
Error: 15 is not over 20
at /home/taehoon/Desktop/playground-nodejs/index.js:4:17
at new Promise (<anonymous>)
at startAsync (/home/taehoon/Desktop/playground-nodejs/index.js:2:10)
at Timeout._onTimeout (/home/taehoon/Desktop/playground-nodejs/index.js:17:20)
at listOnTimeout (internal/timers.js:554:17)
at processTimers (internal/timers.js:497:7)
executor 를 만들 때 조금 더 알아두면 좋은 점들을 살펴보자
executor 내부에서 에러가 throw 되면 reject가 수행됨const throwError = new Promise((resolve, reject) => {
throw Error("error");
});
throwError
.then(() => console.log("throwError success"))
.catch(() => console.log("throwError catched"));
executor 의 리턴 값은 무시된다.// 아무런 영향이 없습니다.
const ret = new Promise((resolve, reject) => {
return "returned";
});
ret
.then(() => console.log("ret success"))
.catch(() => console.log("ret catched"));
Promise {<pending>} // 아직 아무 값도 받지 못함, (성공 / 실패) 여부 불확실
reject 혹은 resolve 만 유효하다.(throw 역시 이전 함수가 호출되면 무시됨)// resolve 만 실행
const several1 = new Promise((resolve, reject) => {
resolve();
reject();
});
several1
.then(() => console.log("several1 success"))
.catch(() => console.log("several1 catched"));
// reject 만 실행
const several2 = new Promise((resolve, reject) => {
reject();
resolve();
});
several2
.then(() => console.log("several2 success"))
.catch(() => console.log("several2 catched"));
// resolve 만 실행
const several3 = new Promise((resolve, reject) => {
resolve();
throw new Error("error");
});
several3
.then(() => console.log("several3 success"))
.catch(() => console.log("several3 catched"));
결과
// 위의 코드들 결과(순서대로)
throwError catched
several1 success
several2 catched
several3 success
어차피 첫번째 resolve, reject만 영향을 주기 때문에 해당 함수가 호출되면 리턴해서 비동기 작업을 빠져나가는 것이 좋다.
위에서 봤던 startAsync를 조금 바꿔보자
function startAsync(age) {
return new Promise((resolve, reject) => {
if (age > 20) {
return resolve(`${age} success`);
}
return reject(new Error(`${age} is not over 20`));
// 이 아래의 코드들은 이제 실행되지 않는다!
});
}
(위의 코드는 무언가를 기다리는 작업은 하고 있지 않다 단순예제로만 보기를!)
정리해보자면
- 함수는 코드가 적힌 순서를 떠나 내가 원할때 코드 조각을 불러와 실행 할 수 있다.
- 콜백은 이런 함수를 인자로 보내 함수 실행의 권한을 다른 함수에 넘기는 것을 말한다.
- 비동기 작업은 이런 함수 여러개가 동시에 실행되고 언제 결과가 나올 지 알 수 없고
- 비동기 작업이라도 서로 의존성이 있는 경우 곤혹을 치를 수 있다.
- 이런 비동기 작업의 문제를 해결하기 위해 Promise 를 이용하고
- 언제끝날지 모르는 비동기 작업의 후속처리를 then,catch로 처리할 수 있다.
출처:
https://elvanov.com/2597 - [Javascript] 비동기, Promise, async, await 확실하게 이해하기