‘[Node.js] 자바스크립트 비동기 개념에 익숙해지기‘는 총 3편에 걸쳐서 콜백 함수, 프로미스, async await 구문을 소개할 예정입니다.
1편에서는 자바스크립트 비동기 개념을 이해하고, 콜백 함수 예제를 작성해봅시다.
1. 자바스크립트 비동기 소개
동기(synchronous) 프로그래밍에서 작업은 차례로 실행되며 작업이 완료될 때까지 중단될 수 없습니다. 모든 작업은 이전 작업의 실행이 완료될 때까지 기다려야 합니다. 반면 비동기(asynchronous) 프로그래밍에서는 임의의 순서로 또는 동시에 작업이 실행될 수 있습니다. Node.js의 이벤트 기반 아키텍 처는 2장에서 이미 배웠습니다. 이제부터 비동기를 처리하는 구체적인 방법을 알아보겠습니다.
자바스크립트는 런타임(브라우저나 Node.js)에서 싱글 스레드로 동작합니다. 싱글 스레드로 동작한다는 것은 한 번에 하나의 작업만 처리할 수 있다는 뜻입니다. 싱글 스레드로 동작하지만 콜백, 프로미스, 어싱크 어웨이트 방법을 사용하면 자바스크립트에서 비동기 처리를 할 수 있습니다. 콜 백은 함수의 파라미터로 함수를 전달하며, 비동기 처리가 끝났을 때 전달된 함수를 실행합니다. 콜백은 가독성이 좋지 못하여 유지보수 및 디버깅이 힘듭니다.
프로미스(Promise 객체 사용)는 콜백 대신 사용할 수 있는 방법으로 비동기 작업이 완료되면 결과를 반환하는 객체입니다. 프로미스 객체는 상태를 가지고 있으며 처음에는 대기였다가 작업이 완 료되면 성공 또는 실패 상태가 됩니다. then( ), catch( ) 메서드를 사용하여 성공과 실패에 대한 처리를 할 수 있습니다.
마지막으로 어싱크 어웨이트(async, await 키워드 사용)는 프로미스를 사용하는 비동기 작업을 동기적으로 처리하는 것처럼 코드를 작성할 수 있게 해줍니다. async가 붙어 있는 함수를 실행할 때 await 키워드를 사용하여 비동기 작업이 완료될 때까지 기다릴 수 있습니다.
2. 콜백 함수 소개
비동기는 현재 코드의 실행 결과를 받지 않고 이후 코드를 수행하는 기법입니다. 컴퓨팅 자원을 효 율적으로 사용하는 기법이지만 정확한 순서를 지켜 수행해야 하는지를 고려해서 처리해야 합니다. 비동기 코드를 순서대로 실행하는 가장 일반적인 방안으로 콜백callback이 있습니다. 콜백은 실행 가 능한 함수를 인자로 전달하여, 특정 상황이 발생할 때 호출되게 하는 방식입니다. 콜백은 현실 세계에서도 발견할 수 있습니다. 커피숍에 가서 점원에게 커피를 먼저 주문하고 다른 것을 하고 있으면, 커피 제조가 끝난 후에 손님을 호출(callback)하는 상황을 콜백으로 볼 수 있습니다.
2.1 콜백 함수 작성해보기
예를 들어 회원 가입이 3단계로 이루어진다고 해봅시다. 회원 가입 API를 호출하면 ❶데이터베이스에저장하고 ❷이메일을보내고 ❸성공메시지를보여주게 됩니다. 이 과정에 사용할 API를 콜백 방식으로 작성하겠습니다.
01. 5장에서 사용할 디렉터리를 먼저 생성하겠습니다. [chapter5] 디렉터리를 생성하고 그 아래에 [callback-promise-async-await] 디렉터리도 생성해주세요.
$ mkdir chapter5 $ cd chapter5 $ mkdir callback-promise-async-await
02 이제부터 작성하는 예제 코드들은 모두 [callback-promise-async-await] 디렉터리에 작성해주세요. 먼저 callback-test.js를 작성하겠습니다.
▼ 콜백 예제
const DB = []; // 회원 가입 API 함수 function register(user) { // ❶ 콜백이 3중으로 중첩된 함수 return saveDB(user, function (user) { // 콜백 return sendEmail(user, function (user) { // 콜백 return getResult(user); // 콜백 }); }); } // ❷ DB에저장후콜백실행 function saveDB(user, callback) { DB.push(user); console.log(`save ${user.name} to DB`); return callback(user); } // ❸ 이메일 발송 로그만 남기는 코드 실행 후 콜백 실행 function sendEmail(user, callback) { console.log(`email to ${user.email}`); return callback(user); } // ❹ 결과를 반환하는 함수 function getResult(user) { return `success register ${user.name}`; } const result = register({ email: "andy@test.com", password: "1234", name: "andy" }); console.log(result);
3단계로 회원 가입 API를 실행하는 ❶ register( ) 함수는 ❷ saveDB( ), ❸ sendEmail( ), ❹ getResult( ) 함수를 각각 차례로 호출해 콜백을 사용했습니다. register( ) → saveDB( ) → sendEmail( ) → getResult( ) 차례로 함수가 실행됩니다. 여기서 보장하는 것은 함수의 실행 순 서입니다. ❸번에서 이메일 발송 후에 기다리지 않고 바로 콜백을 실행합니다.
03 작성한 코드를 실행하겠습니다.
$ node callback-test.js save andy to DB email to andy@test.com success register andy
예상대로 잘 동작합니다. 그런데 매우 간단한 코드인데도 콜백을 사용하는 경우 다소 코드가 복잡 해보입니다. 현실에는 3단계가 아니라 10단계 20단계도 있을 수 있습니다. 그러면 코드가 계속 깊이가 깊어져서 점점 알아보기가 힘든 상황이 됩니다. 콜백의 깊은 곳에서 데이터를 주고받을 때 에러가 발생하면 에러를 추적하기가 어렵습니다. 생각만 해도 머리가 아픈 상황입니다. 그러므로 콜백의 특징을 고려하게 적합한 수준에서 사용해서 낭패를 보지 않게 해야 합니다.
프로미스Promise(프라미스로도 읽음)는 이러한 콜백의 문제를 해결할 목적으로 2015년 ES6 버전 에1 도입됐습니다. 2편에서는 Promise 객체를 알아봅시다.