Node 사용을 위한 기본 ES2015(ES6) 문법과 async/await

백지연·2022년 1월 10일
7

NodeJS

목록 보기
2/26
post-thumbnail

Node는 자바스크립트 실행기이기 때문에 자바스크립트에 대해 꼭 알아야한다. 따라서 Node에 대해 공부하기 전에 ES2015 문법에 대해 공부한다.

이번 포스팅에서는 ES2015 문법인 1. const, let, 2. 템플릿 문자열, 3. 객체 리터럴, 4. 화살표 함수, 5. 구조분해 할당, 6. 클래스, 7. 프로미스, ES2017이지만 너무 중요하므로 추가적으로 8. async/await를 다루겠다.

책 Node.js 교과서(개정 2판) 책의 2강 내용을 참고했다.
+개발 실행 환경 설정 하는 방법
+github 주소


ES2015의 문법을 공부하기 전에, ES2015가 무엇인지 알고 싶었다. 이 블로그(ES6?! ES2015?! ECMAScript란 도대체 무엇인가?)를 보고 이해했다.

아래는 내가 이해한대로 ES2015에 대한 내용을 2줄로 요약해보았다.

1. ES(ECMAScript)는 다양한 환경에서 사용될 수 있는 확장성을 가지고 있다.(웹에 한정X)
2. 자바스크립트 문법은 2015년부터 많은 변화가 있었는데, 그때 변화된 문법이 ES2015(ES6)이다.

1. const, let

const와 let을 알아보기 전에, var을 한번 짚고 넘어가자.
+스코프를 잘 설명해둔 블로그

var

  • var은 함수 스코프를 가지고 있으므로 if문의 블록과 관계없이 접근이 가능하다.
  • ES2015 이전에는 주로 var 변수를 많이 사용했었다. var은 현재 사용하지 않는다!!

var EXAMPLE )
Git [1_const_let/var.js]

입력

if (true) {
  var x = 3;
}
console.log(x); 

출력

3

ES2015 이후에는 var 변수가 constlet으로 완전히 대체 되었다. 이제 const와 let을 알아보자.

const

  • 변수 선언 시 사용
  • 한 번 값을 할당하면 다른 값을 할당할 수 없음
  • const로 선언한 변수를 상수라고 부르기도 함
  • 블록 스코프를 가져서 블록 밖에서는 변수에 접근할 수 없음
  • 그러나, const로 선언된 객체의 안에 있는 값은 바꿀 수 있음

    에러발생
    - 한 번 값 할당 후, 다른 값을 할당하는 경우
    - 초기화 값을 할당하지 않은 경우

const EXAMPLE )
Git [1_const_let/const.js]

입력

if (true) {
  var y = 3;
}
console.log(y); 

출력

Uncaught ReferenceError: y is not defined // error! 블록 밖에서는 변수에 접근 불가

입력2

const a = 0;
a = 1;

출력2

Uncaught TypeError: Assignment to constant variable. // error! 다른 값을 할당하려 함

입력3

const c;

출력3

Uncaught SyntaxError: Missing initializer in const declaration // error! 초기화 값을 할당하지 않음

let

  • 다른 값을 할당해야 할 시 사용
  • 블록 스코프를 가져서 블록 밖에서는 변수에 접근할 수 없음

let EXAMPLE )
Git [1_const_let/let.js]

입력

let b = 0;
b=1;

출력

1

2. 템플릿 문자열

기존 ES5(2009)에서 문자열은 아래의 예만 봐도 가독성이 좋지 않다.

기존 ES5 EXAMPLE )
Git [2_templatestring/es5.js]

입력

var num1 = 1;
var num2 = 2;
var result = 3;
var string1 = num1 + ' 더하기 ' + num2 + '는 \''+ result +'\'';  
console.log(string1);

출력

1 더하기 2'3'

템플릿 문자열

  • ES2015에 생긴 새로운 문자열
  • 키보드 왼쪽 상단의 TAB키 위에 백틱(`)으로 문자열을 감싸는 것
  • 문자열 안에 변수를 넣을 수 있음

템플릿 문자열을 사용한 아래의 예를 보면 코드가 훨씬 깔끔해 진 것을 볼 수 있다.

템플릿 문자열 EXAMPLE )
Git [2_templatestring/templatestring.js]

입력

const num3 = 1;
const num4 = 2;
const result2 = 3;
const string2 = `${num3} 더하기 ${num4}는 '${result2}'`;  
console.log(string2);

출력

1 더하기 2'3'

3. 객체 리터럴

또한 객체 리터럴에 편리한 기능들이 추가되었다.


과거 리터럴 EXAMPLE )
Git [3_objectliteral/oldobject.js]

입력

var sayNode = function() {
  console.log('Node');
};
var es = 'ES';
var oldObject = {
  sayJS: function() {
      console.log('JS');
},
sayNode: sayNode, // (sayNode(속성명): sayNode(변수명))
};
oldObject[es + 6] = 'Fantastic' // ES6이라는 속성 명 생성 시 객체 리터럴 바깥에서 해야 함 
oldObject.sayNode();
oldObject.sayJS();
console.log(oldObject.ES6);

출력

Node
JS
Fantastic

ES2015 리터럴 EXAMPLE )
Git [3_objectliteral/newobject.js]

입력

const sayNode = function() {
  console.log('Node');
};
const es = 'ES';
const newObject = {
  sayJS(){ // 함수 연결 시 :, function을 적지 않아도 됨
      console.log('JS'); 
  },
  sayNode, // 속성명과 변수명이 동일한 경우에는 한 번만 써도 됨
  [es + 6]: 'Fantastic', // 속성명을 객체 안에서 동적으로 설정 가능
};
newObject.sayNode();
newObject.sayJS();
console.log(newObject.ES6);

출력

Node
JS
Fantastic

-> 과거 리터럴에서 ES2015 리터럴로의 변화를 주석으로 표시해두었다.


4. 화살표 함수

화살표 함수(arrow function)라는 새로운 함수가 추가되었고, 기존의 function() {}도 그대로 사용할 수 있다.

기본 사용 EXAMPLE )
Git [4_arrowfunction/exist.js]

입력

// add1~add4는 같은 기능을 하는 함수
function add1(x, y){
  return x + y;
}

const add2 = (x, y) =>{
  return x + y; // 함수 내에 return 문만 존재하는 경우 add3, add4처럼 생략 가능 
};

const add3 = (x, y) => x + y;

const add4 = (x, y) => (x + y);

// not1~not2은 같은 기능을 하는 함수
function not1(x) {
  return !x;
}

const not2 = x => !x;

기존의 함수와 다른 점을 꼽으면 this 바인드 방식이 있다.

this 바인드 방식(function함수 사용) EXAMPLE )
Git [4_arrowfunction/this.js]

입력

var relationship1 = {
  name: 'zero',
  friends: ['nero', 'hero', 'xero'],
  logFriends: function() {
      var that = this; // relationship1을 가리키는 this를 that에 저장 -> 이 문장이 없으면 function이 this를 인식하지 못함.
      this.friends.forEach(function (friend){ // 지금은 forEach는 반복문이라고 이해하면 됨
          console.log(that.name, friend);
      });
  },
};
relationship1.logFriends();

출력

zero nero
zero hero
zero xero

this 바인드 방식(화살표 함수 사용) EXAMPLE )
Git [4_arrowfunction/this.js]

입력

const relationship2 = {
  name: 'zero',
  friends: ['nero', 'hero', 'xero'],
  logFriends(){    
      this.friends.forEach(friend=> { // 화살표 함수로 변경
          console.log(this.name, friend);
      });
  },
};
relationship2.logFriends();

출력

zero nero
zero hero
zero xero

=> 핵심 : function 함수와 화살표 함수가 가리킬 수 있는 this의 범위가 다름!!
+이 블로그(자바스크립트 this, bind 그리고 화살표 함수)를 참고하면 이해하기 좋아보인다.


5. 구조분해 할당

구조분해 할당

  • 객체와 배열로부터 속성이나 요소를 쉽게 꺼낼 수 있게 함
  • 코드의 줄 수를 줄여줌

기존의 속성 할당 EXAMPLE )
Git [5_destructuringassignment/exist_candymachine.js]

입력

var candyMachine = {
  status: {
      name: 'node',
      count: 5,
  },
  getCandy: function(){
      this.status.count--;
      return this.status.count;
  },
};

// 직접 변수에 할당해야 함
var getCandy = candyMachine.getCandy; 
var count = candyMachine.status.count;

구조분해 할당 EXAMPLE )
Git [5_destructuringassignment/new_candymachine.js]

입력

const candyMachine = {
  status: {
      name: 'node',
      count: 5,
  },
  getCandy(){
      this.status.count--;
      return this.status.count;
  },
};

// candyMachine 안의 속성이 찾아져 getCandy와 count 변수가 초기화 됨
const { getCandy, status: { count }} = candyMachine;

--

배열에 대한 기존의 속성 할당 EXAMPLE )
Git [5_destructuringassignment/exist_array.js]

입력

var array = ['nodejs', {}, 10, true];
var node = array[0];
var obj = array[1];
var bool = array[3];

배열에 대한 구조분해 할당 EXAMPLE )
Git [5_destructuringassignment/new_array.js]

입력

const array = ['nodejs', {}, 10, true];
const [node, obj, , bool] = array;

6. 클래스

클래스 문법이 추가되었지만 클래스 기반으로 동작하는 것이 아니라 여전히 프로토타입 기반으로 동작한다. Git [6_class/js_prototype.js]

+프로토타입 관련 내용은 이 블로그(자바스크립트의 프로토타입)를 참고해보자.

클래스 기반 코드 EXAMPLE )
Git [6_class/class.js]

입력

class Human {
  constructor(type = 'human'){ // 생성자
      this.type = type;
  }

  static isHuamn(human){ // human인지 확인하는 메소드
      return human instanceof Human;
  }

  breathe(){
      alert('h-a-a-a-m');
  }
}

class Zero extends Human{
  constructor(type, firstName, lastName){
      super(type);
      this.firstName = firstName;
      this.lastName = lastName;
  }

  sayName(){
      super.breathe();
      alert(`${this.firstName} ${this.lastName}`);
  }
}

const newZero = new Zero('human', 'Zero', 'Cho');
Human.isHuamn(newZero); // true

7. 프로미스

정말 중요한 개념이다!!!!!!

ES2015부터 자바스크립트와 Node의 API들이 콜백(call back) 대신 프로미스(Promise)를 기반으로 재구성된다. 프로미스는 반드시 알아두어야 할 객체이다.

프로미스는 비동기 방식이다.

먼저, 기본적인 프로미스 객체를 생성했다.

프로미스 객체 생성 EXAMPLE )
Git [7_promise/promise.js]

입력

const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => { // resolve, reject를 매개변수로 갖는 call back함수
    if(condition){
        resolve('성공');
    } else{
        reject('실패');
    }
});

promise
    .then((message)=> {
        console.log(message); // 성공(resolve)한 경우 실행
    })
    .catch((error) => {
        console.error(error); // 실패(reject)한 경우 실행
    })
    .finally(() => { // 성공/실패 여부와 상관없이 무조건 실행
        console.log('무조건');
    });

출력

성공
무조건

다음으로, 위의 예를 조금 변형해 만든 연속된 then 사용 예를 들어보겠다.

promise.js를 변형해 만든 연속된 then 사용 EXAMPLE )
Git [7_promise/promise2.js]

입력

const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => { // resolve, reject를 매개변수로 갖는 call back함수
    if(condition){
        resolve('성공');
    } else{
        reject('실패');
    }
});

promise
    .then((message)=> {
        return new Promise((resolve, reject) => {
            resolve(message);
        });
    })
    .then((message2)=> {
        return new Promise((resolve, reject) => {
            resolve(message2);
        });
    })
    .then((message3) => {
        console.log(message3);
    })
    .catch((error) => {
        console.error(error); // 실패(reject)한 경우 실행
    });

출력

성공

콜백을 쓰는 패턴 EXAMPLE )
Git [7_promise/promise_findAndSaveUser.js]

이 방법은 잘 쓰지 않지만 알아만 두자.

입력

function findAndaveUser(Users){
    Users.findOne({}, (err, user)=> { // 첫 번째 콜백
        if (err) {
            return console.error(err);
        }
        user.name = 'zero';
        user.save((err)=>{ // 두 번째 콜백
            if(err){
                return console.error(err);
            }
            Users.findOne({gender:'m'}, (err,user) => { // 세 번째 콜백
                // 생략
            })
        })
    })
}

콜백을 쓰는 패턴(then 중첩) EXAMPLE )
Git [7_promise/promise_findAndSaveUser2.js]

입력

function findAndSaveUser(Users){
    Users.findOne({})
        .then((user) => {
            user.name = 'zero';
            return user.save();
        })
        .then((user) => {
            return Users.findOne({gender: 'm'});
        })
        .then((user) => {
            // 생략
        })
        .catch(err => {
            console.error(err);
        });
}

프로미스에서 마지막으로 다뤄볼 내용은 Promise.all이다.

Promise.all
프로미스가 여러 개 있을 때 Promise.all에 넣으면 모두 resolve될 때까지 기다렸다가 then으로 넘어간다.

중첩된 콜백을 간단히 하는 Promise.all EXAMPLE )
Git [7_promise/promiseAll.js]

입력

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.all([promise1, promise2])
    .then((result) => { // result 매개변수에 각각의 프로미스 결괏값이 배열로 들어있다.
        console.log(result); // ['성공1', '성공2'];
    })
    .catch((error) => { // Promise 중 하나라도 reject가 되면 catch로 넘어온다.
        console.error(error);
    });

출력

[ '성공1', '성공2' ]

8. async/await

async/await

  • 노드 7.6 버전부터 지원되는 기능
  • ES2017에서 추가되었으며 알아두면 편리한 기능
  • 특히 노드에서 비동기 위주로 프로그래밍을 할 때 도움 됨
  • 프로미스는 then과 catch가 계속 반복되는 데, 더 깔끔하게 줄임

아래의 예를 보면
Git [7_promise/promise_findAndSaveUser2.js]와 같은 코드인데, 이전 코드에 비해 코드가 짧아진 것을 볼 수 있다.

async/await 사용 방법

1. 함수명 앞에 async를 붙힘
2. 프로미스 앞에 await를 붙힘

async function으로 변경된 EXAMPLE )
Git [8_async_await/async_await_findAndSaveUser.js]

입력

async function findAndSaveUser(Users){ 
    let user = await Users.findOne({}); // await Users.findOne({})이 resolve될 때까지 기다린 다음에 user 변수를 초기화 함
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({gender:'m'});
    // 생략
}

위의 코드는 에러를 처리하는 부분(프로미스가 reject된 경우)이 없으므로 아래와 같이 try catch를 추가하는 작업이 필요하다.

try catch를 추가한 EXAMPLE )
Git [8_async_await/async_await_findAndSaveUser2.js]

입력

async function findAndSaveUser(Users){
    try{
    let user = await Users.findOne({}); 
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({gender:'m'});
    // 생략
    } catch (error){
        console.error(error);
    }

}

앞에서 알아본 4. 화살표 함수도 async와 같이 사용할 수 있다.

화살표 함수와 같이 쓴 async/await EXAMPLE )
Git [8_async_await/arrowfunction_findAndSaveUser.js]

입력

const findAndSaveUser = async (Users) => {
    try {
    let user = await Users.findOne({}); 
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({gender:'m'});
    // 생략
    } catch (error){
        console.error(error);
    }
};

for문과 async/await을 같이 써서 프로미스를 순차적으로 실행할 수 있다. for문과 함께 쓰는 것은 노드 10버전부터 지원하는 ES2018 문법이다.

for문을 적용한 async/await EXAMPLE )
Git [8_async_await/arrowfunction_findAndSaveUser.js]

입력

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
(async () => {
    for await (promise of [promise1, promise2]){
        console.log(promise);
    }
})();

async/await 관련 최종 정리된 EXAMPLE )
Git [8_async_await/final_findAndSaveUser.js]

입력

async function findAndSaveUser(Users){
    // 생략
}
findAndSaveUser().then(()=> { /*생략*/}); // findAndSaveUser()은 프로미스(Promise)타입을 반환, 따라서 then()을 사용할 수 있음

// 또는

async function other(){
    const result = await findAndSaveUser();
}



최대한 상세하게 다루고 싶었는데, 생각보다 양이 길어질 것 같아서 꼼꼼히 다루지 못한게 아쉽다. 추후에 시간이 나면 중요한 내용을 꼼꼼히 정리를 해보겠다.

잘못된 정보 수정 및 피드백 환영합니다!!

profile
TISTORY로 이사중! https://delay100.tistory.com

2개의 댓글

comment-user-thumbnail
2022년 1월 11일

님 다음장도 해주세용

1개의 답글