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 확실하게 이해하기