Node.js는 비동기 처리 방식을 사용한다는 특징이 있고, 이 특징 덕분에 빠른 속도를 낼 수 있습니다. 하지만 비동기 처리 방식에 익숙하지 않다면, 아래의 코드가 어떻게 작동될지 이해하기 어려울 수 있습니다.
let message;
setTimeout(function() {
message = 'Hello World!'
}, 1000);
console.log(message);
일반적인 프로그래밍 언어라면, message
라는 변수를 만들고, 1초 후에 message
에 “Hello World!”를 넣고, 그 후에 message
를 출력할 것이라고 예측할 수 있을 것 입니다.
하지만, 위 코드를 실행시키면 실행하자 마자 undefined
가 출력됩니다.
그 이유는 프로그램이 실행될때 setTimeout
가 끌날 때 까지 기다려주는 것이 아니라, setTimeout
실행 후, 바로 그 아랫 줄의 console.log
를 실행하기 때문입니다. message
에 “Hello World!”가 들어가는 것은 이미 console.log
가 실행된 후입니다.
이러한 코드의 실행 방식이 비동기 처리 방식입니다. 이런 상황에서 원하는 방식으로 코드를 짜기 위해 Callback, Promise, async / await 를 사용합니다.
Callback
가장 기본적인 방법은 callback
함수를 사용하는 것 입니다.
function delay(callback) {
setTimeout(function() {
callback();
}, 1000);
}
console.log('Before');
delay(function() {
console.log('Finished');
});
console.log('After');
우선, 비동기 작업이 끝난 후에 실행할 코드를 어떤 함수로 만듭니다. (위의 경우에는 익명 함수)
비동기로 처리되는 부분이 있는 함수인 delay
는 callback
이라는 파라미터를 갖고 있습니다. callback
에 아까 만들어둔 함수를 넣어주면, 비동기 작업이 끝났을 때 그 함수를 호출하여 원하는 코드를 실행할 수 있습니다. 이렇게 작업이 끝났을 때 호출할 함수를 콜백 함수라고 합니다.
사실 위의 코드는 굉장히 간단한 예시입니다. setTimeout
함수도 파라미터로 콜백 함수를 받고 있는데, setTimeout
의 콜백 함수에서는 callback()
만을 호출합니다. 이 경우에는 굳이 새로운 익명 함수를 만들 필요 없이, 아래와 같이 callback
을 그대로 넘겨줘도 됩니다.
function delay(callback) {
setTimeout(callback, 1000);
}
Promise
console.log('Before');
new Promise(function (resolve) {
setTimeout(function() {
resolve('Finished');
}, 1000);
}).then(function(result) {
console.log(result);
});
console.log('After');
function delay() {
return new Promise(function (resolve) {
setTimeout(function() {
resolve('Finished');
}, 1000);
});
}
console.log('Before');
delay().then(function(result) {
console.log(result);
});
console.log('After');
Promise는 여러 가지 방법으로 사용할 수 있습니다. 위의 예시는 new Promise()
를 통해 새로운 Promise를 만들고 바로 실행하는 방식이고, 아래의 예시는 return new Promise()
를 반환하는 함수를 만들어두고, 그 함수를 호출하는 방식입니다.
Promise는 실행된 후, 비동기 처리 방식을 따라 끝날 때 까지 기다리지 않고 그 아랫줄의 코드를 바로 실행시킵니다. Promise가 종료된 후에 실행할 코드는 Promise 뒤에 이어지는 then()
안에 함수 형태로 작성해주시면 됩니다. 이때, Promise가 resolve()
를 통해 반환한 값을 파라미터로 받을 수 있습니다. (Promise는 값을 반환할 때 return 대신 resolve를 이용합니다.)
Promise 에러 핸들링
아래 예시는 Promise에서 에러가 발생할 때 처리하는 방법입니다.
new Promise(function (resolve, reject) {
setTimeout(function() {
reject('Catched');
}, 1000);
}).then(function(result) {
console.log(result);
}).catch(function(result) {
console.log('Error ' + result);
});
Promise 안에서 에러가 발생했을 대는 resolve()
대신 reject()
를 이용해서 값을 반환하고, Promise 뒤에 이어지는 catch()
를 붙히고 그 안에 함수 형태로 에러가 발생했을 때 실행할 코드를 작성합니다. 이 함수도 reject()
가 반환한 값을 파라미터로 받을 수 있습니다.
Promise chaining
Promise에서는 아래와 같이 then chaining을 통해 then
을 여러 번 연결할 수 있습니다.
new Promise(function (resolve) {
setTimeout(function() {
resolve(10);
}, 1000);
}).then(function(result) {
return result * 10;
}).then(function(result) {
return result * 10;
}).then(function(result) {
console.log(result);
});
then
을 여러 번 연결 했을 때, 다음 then
은 이전 then
의 반환 값을 파라미터로 받습니다.
async / await
async와 await을 이용하면 비동기 처리 방식을 해결하기 위해 chaining 하던 방식을 사용하지 않고, 코드를 아래로 쭉 쓰는 방식을 사용할 수 있습니다.
함수를 선언할 때 async 함수로 선언하면, 그 함수 안에서는 await을 사용할 수 있습니다.
function delay() {
return new Promise(function (resolve) {
setTimeout(function() {
resolve('Finished');
}, 1000);
});
}
async function run() {
console.log('Before');
const result = await delay();
console.log(result);
console.log('After');
}
run();
위의 코드에서는 run
함수를 async 함수로 선언하고, delay
함수를 호출할 때 await
키워드를 붙혀 실행했습니다. await
이 붙은 Promise는 기존의 비동기 처리 방식과 다르게, 그 Promise가 끝날 때 까지 기다린 후에 다음 줄을 실행합니다.
위 코드의 결과는 기존 예시들과는 다르게 Before, Finished, After 순서로 출력됩니다. 비동기 처리 방식이 아니라 순서대로 실행되는 방식을 원한다면, 비동기로 실행되는 부분을 Promise로 만들어두고 async와 await을 이용해서 코드를 짤 수 있습니다.
aync 함수의 반환형
function delay() {
return new Promise(function (resolve) {
setTimeout(function() {
resolve('Finished');
}, 1000);
});
}
async function run() {
console.log('Before');
const result = await delay();
console.log(result);
console.log('After');
}
async function runWrapper() {
console.log('Before run');
await run();
console.log('After run');
}
runWrapper();
이 코드는 기존 코드에서 run
함수 이전과 이후에 다른 코드를 실행시키는 예시입니다.
run
은 aync 함수이기 때문에, 반환형이 Promise입니다.
즉, 이미 aync 함수로 만들어둔 코드를 await하고 싶다면 new Promise()
를 이용해 새로운 Promise를 만들 필요 없이 그대로 이용하면 됩니다.
async / await 에러 핸들링
아래 예시는 async와 await을 이용할 때 에러를 처리하는 코드입니다.
function delay() {
return new Promise(function (resolve, reject) {
setTimeout(function() {
reject('Catched');
}, 1000);
});
}
async function run() {
console.log('Before');
try {
let result = await delay();
console.log(result);
} catch(error) {
console.log('Error ' + error);
}
console.log('After');
}
run();
에러가 발생할 수 있는 Promise를 호출할 때는 에러 처리 코드를 작성해야 합니다. await을 이용해 Promise를 호출하는 코드를 try
, catch
로 감싸주면 에러가 발생하더라도 안전한 코드를 작성할 수 있습니다.
에러가 발생한 경우, try
안에 있던 코드를 건너뛰고 catch
안의 코드를 실행합니다. 이때 catch
의 파라미터로는 reject()
가 반환한 값이 들어갑니다.