node.js 공부 start

정혅·2025년 4월 2일

공부

목록 보기
6/11

개발환경 설정하기

노드 설치하기

https://nodejs.org

위 사이트에 접속해 Current 버전을 다운로드 받는다.

설치가 완료되면 파워셸창이 실행되며 설치를 진행한다.

완료 후 명령 프롬프트를 실행시켜

# Node.js 버전 확인
node -v
# npm 버전 확인:
npm -v 
  • 위 코드를 입력했을 때 해당 버전들이 정상적으로 뜬다면 정상적으로 설치 완료한 것이다.

LTS 버전 / Current 버전

  • LTS : 기업을 위해 3년간 지원하는 버전으로, 짝수 버전만 LTS 버전이 가능하다.

    • 서버를 안정적으로 운영할 때 사용
  • Current : 최신 기능을 담고 있는 버전으로, 신기능이 필요하거나 학습용으로 사용할 때 적합하다.


ES2015+

ES2015 이상의 자바스크립트를 통틀어 ES2015+ 문법

1. 변수 선언 방법

  • var : 오래된 방식으로, 과거 코드 호환성을 위한 경우에만 사용한다.

    • 함수 스코프 / 재선언 가능 / 호이스팅 발생
  • const : 최신 방식

    • 블록 스코프 / 값 재할당 불가 (상수) / 선언 시 초기화 필수
  • let : 최신 방식

    • 블록 스코프, 재선언 불가, 값 재할당 가능

예제1. const <> var

chrome 에서 about:blank로 가서 F12 개발자 모드를 켜서 콘솔에 들어가 예제를 실행해본다.

코드를 실행하지 않고 줄 바꿈을 하려면 shift + Enter

// var은 함수 스코프를 가지므로 if문의 블록과 관계없이 접근할 수 있다.
if(true){
    var x = 3;
}
console.log(x); //3이 출력




//const는 블록 스코프를 가지므로 블록 밖에서는 변수에 접근할 수 없다. 
if(true){
    const y = 3;
} 
console.log(y); //Uncaught ReferenceError: y is not defined

예제2. const <> let

특징constlet
값 재할당 여부선언 후 값 재할당 불가 = 상수값 재할당 가능
초기화 필요 여부선언과 동시에 초기화 필수선언 시 초기화 선택 가능
스코프블록 스코프(Block Scope)블록 스코프(Block Scope)
const a = 0;
a = 1; //Uncaught TypeError: Assignment to constant variable
//값을 바꾸려고 해서 에러 발

let b = 0;
b = 1; //1

const c; //Uncaught SyntaxError: Missing initializer in const declaration 
//초기화할 때 값을 할당하지 않아 에러 발생 

2. 템플릿 문자열

큰 따옴표 ""작은 따옴표 ''를 이용해 문자열 안에 변수를 넣었는데, 추가로 백틱을 이용해 더욱 편리하게 사용 가능

예제 1 - 일반적 문자열

var num1 = 1;
var num2 = 2;
var num3 = 3;
var string1 = num1 + " 더하기 " + num2 + ` 는 \` ` + result + `\` `;
console.log(string1); //1 더하기 2는 '3'
  • 위 예제는 문자열 string1이 띄어쓰기와 변수, 더하기 기호 때문에 가독성이 좋지 않다. 또한 작은 따옴표를 이스케이프 하느라 코드가 지저분해진다.

예제 2 - ${변수}$ 형식을 이용한 문자열

const num3 = 1;
const num4 = 2;
const result2 = 3;
const string2 = `${num3} 더하기 ${num4}`'${result2}'`;

console.log(string2); //1 더하기 2는 '3'
  • ${변수} 형식으로 변수를 더하기 기호 없이 문자열에 넣을 수 있다. 기존 따옴표 대신 백틱을 사용하므로 큰 따옴표나 작은 따옴표도 함께 사용할 수 있다.

3. 객체 리터럴

속성명(Property Name)변수명(Variable Name) 이 동일하다면, 변수명만 적어도 속성명과 변수명이 같은 객체를 생성할 수 있다. == 단축 속성명(Shorthand Property Names)

예제 1. 단축 속성명

const name = "John";
const age = 30;

/* 전 > 속성명과 변수명을 따로 써야 
const person = {
    name: name,
    age: age
*/
// 후 > 단축 속성명 사용
const person = {
    name,
    age
};

console.log(person); // { name: "John", age: 30 }

예제 2. 메서드 단축 표현 및 대괄호 표기를 이용한 동적 속성 생성

var sayNode = function() {
    console.log('Node');    
};
var es = 'ES';
var oldObject = {
    sayJS: function(){
        console.log('JS');
    },
    sayNode: sayNode,
};

 oldObject[es + 6] = 'Fantastic';
 oldObject.sayNode(); //Node
 oldObject.sayJS(); //JS
 console.log(oldObject.ES6); //Fantastic


//클린 코드 
const newObject = {
    sayJS() { //위 에서 : 를 사용하여 객체 내부에 함수를 정의하는 것 보다 메서드 단축 표현을 이용하는게 훨씬 간단 
        console.log('JS');
    },
    sayNode, //단축 속성명을 사용하여 위에서 정의된 함수를 직접 참조 
    //es는 값이'ES'고, es + 6은 문자열 결합 결과 es의 값인 "ES" + "6"으로 "ES6" 속성이 되는 것이다.
    //대괄호를 사용하여 "ES6" 이라는 속성 이름을 생성하게된다.(동적으로)
    [es + 6]: 'Fantastic',  
};
newObject.sayNode(); //Node
newObject.sayJS(); //JS
console.log(newObject.ES6); //Fantastic

sayNode 와 같이 단축 속성명을 사용하면, 키와 값이 동일한 변수일 때 단순히 변수명만 사용해도 된다. > 위 클린 코드에서는 외부 변수를 참조하기 때문에 자동으로 키:값 쌍을 생성한다.


4. 화살표 함수

=> 기호를 이용해 함수를 선언한다.

//일반적 함수 표현식
const add = function(a, b) {
    return a + b;
};

//화살표 함수로 변환
const add = (a, b) => {
    return a + b;
 };
 //단일 표현식일 경우 중괄호와 return 생략 가능
 const add = (a, b) => a + b;

특징 비교

상황일반 함수화살표 함수
this 동작호출 방식에 따라 변경상위 스코프의 this 고정
내부 함수에서 this 해결that = this 방식 필요별도 처리 없이 상위 스코프의 this 사용
객체 메서드 정의적합(this 가 객체를 정확히 참조)부적합(this가 상위 스코프를 참조하므로 undefined 발생 가능)
비동기 작업에서 사용콜백 내에서 this가 불안정할 수 있음this가 유지되므로 간결하고 직관적으로 사용 가능

예제 1. 일반 함수와 화살표 함수의 this 차이

const obj = {
  name: "Hyunji",
  arrowFunc: () => {
    console.log(this.name); // 상위 스코프의 `this`를 참조 (window/undefined)
  },
  regularFunc: function () {
    console.log(this.name); // 현재 객체인 obj를 참조
  }
};

obj.arrowFunc(); // undefined (window 또는 글로벌 스코프를 참조)
obj.regularFunc(); // Hyunji (obj를 참조)

예제 2. 내부 함수에서 this 문제 해결

화살표 함수는 자체적인 this를 가지지 않고 상위 스코프의 this를 참조한다.

일반 함수는 내부 함수에서 this를 참조하지 못하기 때문에 that = this 방식을 사용하거나, 화살표 함수로 해결한다.

var relationship1 = {
  name: 'zero',
  friends: ['nero', 'hero', 'xero'],
  logFriends: function () {
    var that = this; // relationship1을 가리키는 this를 that에 저장
    this.friends.forEach(function (friend) {
      console.log(that.name, friend); // `that`을 참조
    });
  },
};
relationship1.logFriends(); // 출력: zero nero, zero hero, zero xero

const relationship2 = {
  name: 'zero',
  friends: ['nero', 'hero', 'xero'],
  logFriends() {
    this.friends.forEach(friend => {  //화살표 함수 사용으로 
      console.log(this.name, friend); //logFriends의 상위 스코프인 relationship2의 this를 의미 
    });
  },
};
relationship2.logFriends();

예제 3. 객체 메서드 정의

객체 메서드 정의에서는 일반 함수가 적합 > 화살표 함수의 this는 상위 스코프를 참조하므로 예상치 못한 결과를 초래할 수 있다.

const obj = {
    name: "Bob",
    greet: function() {
        console.log(this.name); // 일반 함수: this는 obj를 가리킴
    },
    greetArrow: () => {
        console.log(this.name); // 화살표 함수: this는 상위 스코프(window 또는 undefined)를 참조
    },
};
obj.greet(); // Bob
obj.greetArrow(); // undefined

예제 4. 비동기 함수에서 this 처리

비동기 함수나 콜백 함수에서는 화살표 함수가 this를 고정하기 때문에 유리하다.

class Timer {
    constructor() {
        this.seconds = 0;
    }

    start() {
        setInterval(() => {
            this.seconds++;
            console.log(this.seconds); // this는 Timer 인스턴스를 참조
        }, 1000);
    }
}
const timer = new Timer();
timer.start();
// 1, 2, 3, ...

5. 구조 분해 할당

객체와 배열로부터 속성이나 요소를 손쉽게 추출해서, 변수에 할당하는 문법

객체의 속성을 바로 변수로 할당하기에, 별도 선언 없이 객체의 키 이름을 그대로 변수 이름으로 사용할 수 있다.

동작 원리

const obj = { a: 1, b: 2 };
const { a, b } = obj; // obj 객체에서 a, b를 추출해 각각 변수로 저장
console.log(a); // 1
console.log(b); // 2
  • 객체나 배열의 특정 값을 변수로 추출하고, 해당 속성에 값을 할당하는 문법

  • 중괄호 안에 명시된 경로에 따라 객체 내부의 값을 찾고, 해당 값을 새로운 변수에 할당

예제 1. 객체에 대한 구조 분해 할당

객체 속성을 기준으로 따로 변수 선언 없이 값을 꺼내올 수 있다.

const candyMachine = {
    status: { //내부에 status 객체가 존재 name과 count 속성 포함 
        name: 'node',
        count: 5,
    },
    getCandy(){ //메서드 
        this.status.count--; //값 반환 후 감소 
        return this.status.count;
    },
}

const { getCandy, status: { count }} = candyMachine; //구조 분해 = 객체의 속성 키 이름을 그대로 사용해 변수를 자동 생
/* 위 코드를 구조 분해 없이 풀어서 쓰면
const getCandy = candyMachine.getCandy;  
const count = candyMachine.status.count; */

console.log(getCandy); //  [Function: getCandy] getCandy 함수 자체 (참조)
console.log(count); // 5 (status.count의 값)
  • candyMachine 객체 정의:

    • 이 코드는 candyMachine이라는 객체를 정의하고 있다.

    • candyMachine 객체 안에는 두 가지 주요 요소가 있다.

      • status: 또 다른 객체로, 그 안에 namecount라는 두 개의 키-값 쌍이 들어 있다.

      • getCandy: 메서드(일반 함수)로 정의된 함수다.

  • status의 구조:

    • status는 객체 내부에 있는 또 하나의 객체다.

    • status의 키와 값은

      • name: 'node' (문자열 데이터)

      • count: 5 (숫자 데이터)

  • getCandy 함수:

    • 이 함수는 this.status를 참조하여, count 값을 하나 줄이고(this.status.count--), 줄어든 값을 반환한다(return this.status.count).

    • 여기서 thiscandyMachine 객체를 참조한다. 즉, this.statuscandyMachine.status를 뜻한다.

  • const { getCandy, status: { count } } = candyMachine; >> 중첩된 객체에서 값을 추출

    • const 키워드

      • const는 변수를 선언하겠다는 의미로, getCandycount라는 두 변수를 선언하겠다는 의미
    • 중괄호 {}를 사용한 구조 분해

      • getCandy: 객체 candyMachine 의 속성 중 getCandy를 추출하여 변수 getCandy 변수에 할당

      • status: { count } : 객체 candyMachinestatus 속성에 접근한 뒤, 그 안에 있는 count 값을 추출해 변수 count에 할당한다.

      status 속성 안에서 count 값을 추출해 중첩된 속성을 쉽게 가져올 수 있는 것이 구조 분해 할당의 장점

예제 1-1. 변수 이름 변경이 필요한 경우

구조 분해로 변수를 만들 때, 다른 이름으로 사용할 수 있다.

const { getCandy: myCandyFunction, status: { count: candyCount } } = candyMachine;

console.log(myCandyFunction); // candyMachine.getCandy
console.log(candyCount);      // candyMachine.status.count
  • getCandymyCandyFunction 이라는 변수로 저장

  • status.countcandyCount라는 변수로 저장


예제 2. 배열 구조 분해 할당

기본 동작

  • 배열의 인덱스 순서에 따라 값을 꺼내 변수를 초기화 한다.

    중괄호{}는 객체를 정의하거나 생성할 때 사용

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

console.log(node); //nodejs
console.log(obj); //{} > 빈 객체 참조 = 객체를 값으로 저장 
console.log(bool); //true

위와 같은 코드를 아래와 같이 할 수 있다

const array = ['nodejs', {}, 10, true];
const [node, obj, , bool] = array;
  • ,(콤마)는 건너뛰기를 의미한다. array[2] 의 값인 10은 건너뛰어 변수로 할당되지 않고 bool 변수에 true가 할당된다.

객체 구조 분해는 키로 접근

배열 구조 분해는 인덱스로 접근


6. 상속과 객체 생성

클래스 기반 문법과 프로토타입 기반 문법

인스턴스(Instance) 의 개념

  • 클래스나 생성자 함수로부터 생성된 개별적인 객체
function Human(name) {
  this.name = name;
}

const person1 = new Human('Hyunji');
const person2 = new Human('Zero');

console.log(person1.name); // Hyunji
console.log(person2.name); // Zero
  • 생성자 함수로 생성한 인스턴스
class Human {
  constructor(name) {
    this.name = name;
  }
}

const person1 = new Human('Hyunji');
const person2 = new Human('Zero');

console.log(person1.name); // Hyunji
console.log(person2.name); // Zero
  • 클래스 기반 인스턴스

예제 1. 프로토타입 기반 상속

var Human = function(type) {
  this.type = type || 'human'; // type 기본값 설정
};

Human.isHuman = function(human) {
  return human instanceof Human; // human이 Human의 인스턴스인지 확인하는 정적 메서드
};

Human.prototype.breathe = function() {
  alert('h-a-a-a-m'); // 프로토타입 메서드
};

// Zero 생성자 함수 정의
var Zero = function(type, firstName, lastName) {
  Human.apply(this, arguments); // Human의 생성자 호출 > this 초기화 
  this.firstName = firstName;
  this.lastName = lastName;
};

// 상속 구현
Zero.prototype = Object.create(Human.prototype); // Human의 프로토타입을 상속받음
Zero.prototype.constructor = Zero; // Zero 생성자를 다시 설정
Zero.prototype.sayName = function() {
  alert(this.firstName + ' ' + this.lastName); // Zero만의 메서드
};

var oldZero = new Zero('human', 'Zero', 'Cho');
console.log(Human.isHuman(oldZero)); // true

Object.create() : 주어진 객체를 프로토타입으로 설정한 새로운 객체를 생성 > 새로 생성된 객체는 ()안에 있는 객체를 상속 받게 되는 것 >> 자바스크립트에 내장되어 있는 함수

const proto = { greet: function() { console.log('Hello!'); } };

const obj = Object.create(proto);
obj.greet(); // 'Hello!' (proto 객체의 메서드 사용 가능)


// 위 함수로 생성한 객체는 프로토타입 체인을 통해 주어진 객체와 연결된다.
console.log(Object.getPrototypeOf(obj) === proto); // true

예제 2. 클래스 기반 상속

  • ES6에서 도입된 클래스 문법을 활용해 상속 구현

    • 프로토아입 기반보다 더 간결하고 직관적
class Human {
  constructor(type = 'human') {
    this.type = type; // type 기본값 설정
  }

  static isHuman(human) {
    return human instanceof Human; // 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}`); // Zero만의 메서드
  }
}

const newZero = new Zero('human', 'Zero', 'Cho');
console.log(Human.isHuman(newZero)); // true
  • 프로토타입 기반 상속에서 썼던 Object.createprototype.constructor를 직접 설정할 필요가 없다.

클래스 문법의 주요 특징

1) 선언

클래스는 class 키워드를 사용하여 선언하며, 생성자 함수와 상속을 포함한 객체 생성 및 관리가 더 간편해진다.

2) 정적 메서드

static 키워드로 클래스에 속한 메서드를 정의한다. 정적 메서드는 클래스 자체에 속하며, 인스턴스에서는 호출할 수 없다.

3) 인스턴스 메서드

클래스 안에서 정의된 메서드는 각 인스턴스에서 사용할 수 있다.

4) 상속

extends 키워드로 부모 클래스를 상속받는다. super() 를 사용해 부모 클래스의 생성자와 메서드를 호출할 수 있다.

5) 문법 간결함

프로토타입 기반 코드를 작성할 때 필요한 Object.createprototype.constructor 설정을 자동으로 처리한다.


7. 프로미스

콜백 대신 프로미스 함수를 사용한다.

프로미스 객체의 세 가지 상태

  1. Pending(대기 상태)

    • 프로미스가 처음 생성되었을 때의 상태로, 작업이 아직 완료되지 않은 상태

    • resolve()reject()가 호출되기 전의 상태

  2. Fulfilled(이행됨)

    • 작업이 성공적으로 완료되고, 결과 값을 반환하는 상태

    • resolve(값)이 호출되면서 then() 메서드 실행

  3. Rejected(거부됨)

    • 작업이 실패한 상태로, 에러가 반환된다.

    • reject(에러) 가 호출되면서 catch() 메서드 실행

Peding 에서 Fulfilled 또는 Rejected 로 상태가 바뀌며, 이후에는 더 이상 상태가 변경되지 않는다 (불변 상태)

resolve()와 reject()

  • 두 함수 모두 Promise 생성 함수 내 호출되어 상태 전환을 담당한다.

  • resolve(값)

    • 프로미스의 상태를 Fulfilled 로 변경

    • resolve를 호출하면 리턴값이 then()으로 넘어간다

  • reject(에러)

    • 프로미스의 상태를 Rejected로 변경한다.

    • reject를 호출할 때 리턴값(에러 메시지 등)이 catch()로 넘어간다.

finally 부분은 무조건 실행

예제 1. 기본 Promise와 상태 전환

new Promise를 사용해 비동기 작업을 나타내는 프로미스를 생성

const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => {
  if (condition) {
    resolve('성공');
  } else {
    reject('실패');
  }
});
// 다른 코드가 들어갈 수 있음
promise
  .then((message) => {
    console.log(message); // 성공(resolve)한 경우 실행
  })
  .catch((error) => {
    console.error(error); // 실패(reject)한 경우 실행
  })
  .finally(() => { // 끝나고 무조건 실행
    console.log('무조건');
  });


//성공
//무조건 

예제 2. 프로미스 체이닝

const promise = new Promise((resolve, reject) => {
  resolve('성공');  //resolve를 이용해 성공 상태로 전환 
});


promise
  .then((message) => {
    return new Promise((resolve, reject) => { //새로운 Promise 객체를 생성해 이어받기 
      resolve(message);
    });
  })
  .then((message2) => {
    console.log(message2);
    return new Promise((resolve, reject) => {
      resolve(message2);
    });
  })
  .then((message3) => {
    console.log(message3);
  })
  .catch((error) => {
    console.error(error);
  });
  1. then() 블록은 값을 반환하거나 새 프로미스를 반환해야 다음 단계로 연결된다.

  2. 이전 단계에서 받은 값으로 새 프로미스를 생성하여 다음 then()으로 연결할 수 있다.

  3. 만약 then()에서 아무것도 반환하지 않으면, 기본적으로 undefined가 다음 단계로 전달된다.

예제 3. 콜백 함수 대신 Promise

//콜백 방식
function findAndSaveUser(Users) { 
  Users.findOne({}, (err, user) => { // 첫 번째 콜백 - DB에서 사용자 찾기 
    if (err) { 
      return console.error(err); 
    }
    user.name = 'zero'; //찾은 사용자의 이름을 변경 
    user.save((err) => { // 두 번째 콜백 - 변경된 사용자 데이터를 저장 
      if (err) { 
        return console.error(err); 
      }
      Users.findOne({ gender: 'm' }, (err, user) => { // 세 번째 콜백 - 조건에 맞는 사용자 다시 검색 
        // 생략
      });
    });
  }); 
}


//Promise 방식

function findAndSaveUser(Users) { 
  Users.findOne({}) //Promise를 반환하여 첫 번째 사용자 검색
    .then((user) => {
      user.name = 'zero';
      return user.save(); // 저장 결과 반환
    })
    .then((user) => {
      return Users.findOne({ gender: 'm' }); // 새로운 사용자 검색
    })
    .then((user) => {
      // 이후 작업
    })
    .catch(err => { 
      console.error(err); // 모든 과정에서의 에러 처리
    });
}
  • Promise 방식은 비동기 작업을 then() 체이닝을 통해 순차적으로 처리하며, 코드가 훨씬 간결하고 읽기 쉬워진다.

  • 모든 에러는 catch()에서 한 번에 처리한다.

예제 4. Promise 내장 함수를 이용한 한번에 실행

Promise.all()

  • 여러 개의 Promise를 배열로 받아 모든 Promise가 성공할 때 결과를 반환한다.

    • 모든 Promise의 결과값이 배열 형태로 반환

    • 하나라도 실패(reject)하면, 즉시 에러로 처리

    성공: 모든 Promise가 resolve되면 then으로 결과가 전달

    실패: 하나라도 reject되면 catch로 처리

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.all([promise1, promise2])
  .then((result) => {
    console.log(result); // ['성공1', '성공2'];
  })
  .catch((error) => {
    console.error(error);
  });

Promise.allSettled()

  • 배열의 모든 Promise가 완료될 때까지 기다린 후, 성공(fulfilled)와 실패(rejected) 상태를 반환한다.

    • 각 Promise의 상태롸 결과를 객체 형태로 반환

    • 성공/실패 여부와 관계없이 모든 Promise가 완료된 후 결과를 볼 수 있다.

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.allSettled([promise1, promise2])
  .then((result) => {
    console.log(result); 

  })
  .catch((error) => {
    console.error(error);
  });

   /* [
      { status: 'fulfilled', value: '성공1' },
      { status: 'fulfilled', value: '성공2' }
    ]
      */

Promise.resolve()

  • 즉시 resolved 상태의 Promise를 생성한다.

    • 초기값 설정이나 비동기 작업 없이 즉시 성공 값을 반환할 때 사용된다.

      • 상태가 이미 resolved로 설정된 Promise를 반환한다.
const promise = Promise.resolve('성공');
promise.then((result) => {
  console.log(result); // '성공'
});

Promise.reject()

  • 즉시 rejected 상태의 Promise를 생성한다.

    • 비동기 작업 실패를 표현하거나 에러를 처리할 때 사용된다.

      • 상태가 이미 rejected로 설정된 Promise를 반환한다.
내장 함수설명특징
all여러 비동기 작업(여러 개의 프로미스를 동시에 실행)이 모두 성공해야 결과 반환(then). 하나라도 실패하면 에러 처리.(catch)- 성공 시 결과를 배열로 반환
- 실패 시 빠르게 에러 처리.
allSettled모든 비동기 작업이 완료되면 성공(fulfilled)과 실패(rejected) 상태를 반환.- 각 상태와 결과를 객체로 반환.
- 성공/실패 여부와 관계없이 결과 확인 가능.
- 실패한 것만추릴 수 있다.
resolve즉시 성공(resolved) 상태의 결과 생성.- 비동기 작업 없이 성공값 반환.
- 초기값 설정에 유용.
reject즉시 실패(rejected) 상태의 결과 생성.- 에러 상황 표현이나 테스트 시 유용.
- 즉시 에러값 반환.

8. async/await

Promise는 비동기 작업의 성공(resolve) 나 실패(rejected)를 처리하기 위한 객체 > 비동기 작업의 결과를 then, catch 등의 메서드로 처리 가능

asyncawait 는 ES8에서 도입된 문법으로, Promise를 더 직관적으로 처리하기 위한 도구이다. 그러나 rejected를 처리하려면 try~catch 이용해야한다.

async/await의 개념

  1. async

    • 함수 앞에 (function) 붙여서 해당 함수를 비동기 함수로 선언한다.
    • 반환값은 무조건 Promise 객체다.
      • 함수 내부에서 명시적으로 Promise를 반환하지 않더라도, 함수의 반환값은 자동으로 Promise.resolve()로 감싸진다.
    • 내부적으로 Promise를 반환하므로 then() 또는 await을 사용해 결과를 처리할 수 있다.
  2. await

    • await 키워드는 async 함수 내부에서만 사용 가능하다.
    • Promise가 해결될 때까지 기다렸다가 프로미스가 resolve 상태가 되면 그 값을 반환하고, rejected 상태라면 에러를 던진다.
    • await은 Promise가 끝날 때까지 코드 실행을 일시 중단한다. > Promise가 resove 상태가 될 때까지 기다림
      • 비동기 코드지만 동기적인 느낌으로 작성
//async
async function example() {
    return "Hello, World!";
}

example().then((result) => {
    console.log(result); // "Hello, World!"
});
//await
async function fetchData() {
    const result = await new Promise((resolve) => {
        setTimeout(() => resolve("비동기 작업 완료!"), 1000);
    });
    console.log(result); // "비동기 작업 완료!" (1초 후 출력)
}

fetchData();

  • 위에서 공부한 프로미스 패턴 코드 Async/await 으로 한번 더 축약 가능
  function findAndSaveUser(Users) {
    Users.findOne({})
      .then((user) => { //실행이 완료되면 결과값이 user로 들어가서 실행 
        user.name = 'zero';
        return user.save();
      })
      .then((user) => {
        return Users.findOne({ gender: 'm' });
      })
      .then((user) => {
        // 생략
      })
      .catch(err => {
        console.error(err);
      });
  }

예제 1. async function의 도입

  • 변수 = await 프로미스; 인 경우 프로미스가 resolve된 값이 변수에 저장

  • 변수 await; 값인경우 그 값이 변수에 저장

async function findAndSaveUser(Users) {
let user = await Users.findOne({}); //순서가 오른쪽에서 왼쪽 순으로 진행된다고 생각하면 된다.
user.name = 'zero';
user = await user.save();
user = await Users.findOne({ gender: 'm' }); //마찬가지로 오른쪽에서 왼쪽 
// 생략
}

예제 1-1 Promise 객체를 받는 방법

  • 비동기 함수인 async 함수는 언제나 Promise 객체를 반환한다.
    • 비동기 함수 내부에서 반환되는 값은 자동으로 Promise.resolve()로 감싸져서 반환된다.(해당 작업의 완료 상태)
//개념적 예제 1
const promise = new Promise(...)

promise.then((result) => ...)

async function main() {

    const result = await promise;
    return result;      
}

//아래 두 방법 중 택 1
main().then((name) => ...) //비동기의 반환값에 대해 호출하면서 then을 호출해야한다. > main().then()

const name = await main() //위와 같은 방법 또는 await을 사용해  작성 가능하다.

//응용적 예제 2
async function findAndSaveUser(Users) {
  // 생략
}

findAndSaveUser().then(() => { /* 생략 */ });
// 또는
async function other() {
  const result = await findAndSaveUser();
}

예제 2. rejected 상태일 때 에러 처리 방법

//개념적 예제 1
const promise = new Promise((resolve, reject) => {
  reject('Promise rejected!'); // 에러 발생
});

async function main() {
  try {
    const result = await promise; // Promise의 결과를 기다림
    return result; // 성공 시 결과 반환
  } catch (error) {
    console.error('에러 처리:', error); // 실패 시 에러 메시지 출력
  }
}

main();


//응용 예제 2
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);
  }
};
  • Promise가 reject 상태가 되면 자동으로 catch 블록으로 전달되어 에러 메시지를 받는다.

예제 3. for await of () - 프로미스 반복

await을 사용하기 때문에 async 함수 안에서 사용해야한다.

각 Promise의 결과를 비동기적으로 처리하는 예제

  • for await (변수 fo 프로미스 배열)
const promise1 = Promise.resolve('성공1'); //Promise.resolve : 즉시 resolved 상태의 Promise 생성 
const promise2 = Promise.resolve('성공2');
(async () => {
  for await (promise of [promise1, promise2]) {
    console.log(promise);
  }
})();
  • for-await-of 구문은 비동기 이터러블 객체를 순회하며, 각 Promise의 resolve 결과를 비동기적으로 처리한다.

    • Promise가 해결될 때까지 기다린 후 값을 처리한다.

Map/Set

1. Map

  • 키와 값을 저장하는 자료구조 > key와 value의 자료형이 자유롭다.
  • 속성들 간의 순서를 보장하고 반복문을 사용할 수 있다.
    • 속성명으로 문자열이 아닌 값도 사용할 수 있고 size메서드를 통해 속성의 수를 쉽게 알 수 있다는 점에서 일반 객체와 다르다.
const m = new Map(); //Map 생성

m.set('a', 'b'); //set(키, 값)으로 Map에 속성 
m.set(3, 'c'); //문자열이 아닌 값을 키로 사용 가능

const d = {}; //객체 생성
m.set(d, 'e'); //객체도 키로 사용 가능 > 계속 비어 있는 객체로 유지되고 값만 바뀜 

m.set({a: 'b'}, { c: 'd'});  //key도 객체, value도 객체 사용 가능 
m.get({a: 'b'}); // 그러나 undefined > 위 {a: 'b'}와 여기 객체는 서로 다른 객체임
({ a: 'b' }) !== ({ a: 'b'}) // true > 객체는 참조타입으로 동작하기에 객체를 생성해 변수에 넣어 설정해줘야함

//해결책 > 변수에 객체를 저장 후 사용해야 같은 곳을 참조
const obj = { key : 'key' }
m.set(obj, 123);
m.get(obj); //123


m.get(d); //get(key) 로 속성값 조회
console.log(m.get(d)); //e

m.size; //속성 개수 조회 !== length와 다
console.log(m.size); //3

for(const [a, b] of m) {
  console.log(a, b); //'a', 'b', 3, 'c', {}, 'e'
} //속성 간의 순서도 보장한다.

m.forEach((a, b) => { 
  console.log(a, b);
  }); //forEach도 사용 가능하다. 결과는 위와 동일

  m.has(d); //has(key)로 속성 존재 여부를 확인한다.
  m.delete(d); //delete(key)로 속성을 삭제한다.
  m.clear(); //clear()로 모든 키와 값을 전부 제거한다.
  console.log(m.size); //0

Javascript의 참조 타입 개념

  • 원시 타입(Primitive Types) : 숫자, 문자열, boolean, null, undefined, symbol 등은 값 자체를 비교한다.
  • 참조 타임(Reference Types) : 객체, 배열, 함수 등을 메모리 주소(참조)를 비교한다.
//원시타입
const a = 10;
const b = 10;
console.log(a === b); // true (값 비교)


//참조 타입
const obj1 = { key: 'value' };
const obj2 = { key: 'value' };
console.log(obj1 === obj2); // false (다른 메모리 주소를 참조)

따라서 Map에서 키나 값을 가져왔을 때 참조 타입이면 동일한 구조를 가진 값이라도 메모리주소가 다르면 같지 않다고 판단한다.

해결책

const objKey = { key: 'value' };
const map = new Map();

// 키를 동일한 참조로 사용
map.set(objKey, 'someValue');

// 같은 참조로 접근
console.log(map.get(objKey)); // 'someValue'

// 변수에 저장된 객체를 활용하여 비교
const value = map.get(objKey);
console.log(value === 'someValue'); // true

2. Set

  • Set은 중복을 허용하지 않는다.

    • 따라서 배열 자료구조를 사용하고 싶으나 중복은 허용하고 싶지 않을 때 또는 기존 배열에서 중복을 제거하고 싶을 때도 Set을 사용하면 된다.
const s = new Set();
s.add(false); // add(요소)로 Set에 추가한다.
s.add(1);
s.add('1');
s.add(1); //중복이므로 자동으로 무시된다.
s.add(2);

console.log(s.size); //중복이 제거되어 4 출력

s.has(1); //has(요소)로 요소 존재 여부를 확인한다.
console.log(s.has(1)); // true

for(const a of s) {
  console.log(a); //false 1 '1' 2
}

s.forEach((a) => {
  console.log(a); //false 1 '1' 2
}

s.delete(2); //delete(요소)로 해당 요소를 제거한다.
s.clear(); // clear()로 전부 제거한다.

기존 배열에서 중복을 제거하고 싶을 때

arr1 = [ 1, 1, 2, 2, 3, 3];
const s2 = new Set(arr1);
const result2 = Array.from(s2); 
console.log(result2); //[1, 2, 3]

0개의 댓글