소프트웨어 기술(스타트업 위주)

javascript

javascirpt, ECMASCRIPT2015 기준 정리입니다. 구체적인 것은 modernjavascipt가 더 구체적입니다. 개괄적인 개념을 설명드립니다.


JavaScript 기본 문법 정리

1.기본문법

console.log('Hello, world!')

1.1 엄격 모드

'use strict'
x = 10 // 오류 발생 (선언되지 않은 변수)
console.log(x)

1.2 변수와 상수

let name = 'Alice'
const PI = 3.14
var age = 25

1.3 자료형

let number = 10
let text = 'Hello'
let isTrue = true
let empty = null
let notDefined
let uniqueID = Symbol('id')
let person = { name: 'Alice', age: 25 }
let numbers = [1, 2, 3]
function greet() {
  console.log('Hello!')
}

1.4 alert, prompt, confirm을 이용한 상호작용

alert('안녕하세요!')
let name = prompt('이름을 입력하세요:', '익명')
let isAdult = confirm('당신은 성인입니까?')

1.5 형 변환

let num = '123'
console.log(Number(num))
console.log(String(456))
console.log(Boolean(0))
console.log(Boolean(1))

1.6 기본 연산자와 수학

console.log(5 + 2)
console.log(5 - 2)
console.log(5 * 2)
console.log(5 / 2)
console.log(5 % 2)
console.log(2 ** 3)

1.7 비교 연산자

console.log(5 > 3)
console.log(5 == '5')
console.log(5 === '5')
console.log(5 !== '5')

1.8 if?를 사용한 조건 처리

let age = 18
if (age >= 18) {
  console.log('성인입니다.')
} else {
  console.log('미성년자입니다.')
}
let result = age >= 18 ? '성인' : '미성년자'
console.log(result)

1.9 논리 연산자

console.log(true && false)
console.log(true || false)
console.log(!true)

1.10 Nullish 병합 연산자 (??)

user가 null 이거나 undefined 면 '익명' 출력하고 아니면 user 그대로 사용

let user
console.log(user ?? '익명')

1.11 while과 for 반복문

let i = 0
while (i < 3) {
  console.log(i)
  i++
}
for (let j = 0; j < 3; j++) {
  console.log(j)
}

1.12 switch문

let fruit = 'apple'
switch (fruit) {
  case 'apple':
    console.log('사과입니다.')
    break
  case 'banana':
    console.log('바나나입니다.')
    break
  default:
    console.log('알 수 없는 과일입니다.')
}

1.13 함수

function add(a, b) {
  return a + b
}
console.log(add(3, 5))

1.14 함수 표현식

let subtract = function (a, b) {
  return a - b
}
console.log(subtract(10, 4))

1.15 화살표 함수 기본

const multiply = (a, b) => a * b
console.log(multiply(3, 5))

객체: 기본

2.1 객체

let person = {
  name: 'Alice',
  age: 25,
  greet: function () {
    console.log('Hello, ' + this.name)
  },
}
console.log(person.name)
person.greet()

2.2 참조에 의한 객체 복사

let user1 = { name: 'Alice' }
let user2 = user1
user2.name = 'Bob'
console.log(user1.name) // "Bob"

2.3 가비지 컬렉션

JavaScript에서 가비지 컬렉션(Garbage Collection, GC) 은 메모리를 자동으로 관리해주는 아주 중요한 기능이에요. 쉽게 말해서 더 이상 쓰이지 않는 데이터를 자동으로 정리해주는 청소부예요 v8 엔진은 자동으로 처리 해줌.

  • 글로벌 변수 불필요한 것 제거
  • 이벤트 리스너 제거 해야함
let obj = { name: 'Temp' }
obj = null // 가비지 컬렉션 대상이 됨

2.4 메서드와 this

  1. 전역 (브라우저 or Node)

브라우저 환경:

console.log(this); // 👉 window

Node.js 환경:

console.log(this); // 👉 {}
  • 브라우저에선 window, Node.js에선 빈 객체(module.exports 또는 {})를 참조

  1. 일반 함수에서의 this
function show() {
  console.log(this);
}
show(); // 👉 window (브라우저), undefined (Node.js에서 strict mode일 때)
  • 일반 함수는 전역 객체(window) 또는 strict mode에서는 undefined를 참조합니다.

  1. 객체의 메서드에서의 this
const person = {
  name: "Alice",
  sayHi() {
    console.log(this.name);
  }
};
person.sayHi(); // 👉 "Alice"
  • 메서드를 호출한 객체 자신을 참조합니다.

  1. 화살표 함수에서의 this (❗매우 중요)
const obj = {
  name: "Bob",
  arrow: () => {
    console.log(this.name);
  }
};
obj.arrow(); // 👉 undefined (this는 obj가 아님!)
  • 화살표 함수는 자신만의 this가 없고, 상위 스코프의 this를 그대로 사용합니다.

추가 예시:

function outer() {
  const arrow = () => console.log(this);
  arrow();
}
outer(); // 👉 전역 (window 또는 {})

const obj = {
  test: function () {
    const arrow = () => console.log(this);
    arrow();
  }
};
obj.test(); // 👉 obj (상위 스코프의 this)

  1. 생성자 함수 / 클래스에서의 this
function Person(name) {
  this.name = name;
}
const p = new Person("Charlie");
console.log(p.name); // 👉 "Charlie"
class Animal {
  constructor(type) {
    this.type = type;
  }
}
const cat = new Animal("cat");
console.log(cat.type); // 👉 "cat"
  • 생성자 함수나 클래스에서는 새로 생성되는 인스턴스 객체를 참조합니다.

  1. 이벤트 핸들러에서의 this
document.querySelector("button").addEventListener("click", function () {
  console.log(this); // 👉 클릭된 button 요소
});
  • 이벤트 핸들러(일반 함수)의 경우, 이벤트가 발생한 DOM 요소를 참조합니다.
  • 화살표 함수로 사용하면 상위 스코프의 this(주로 window)를 참조합니다.

  1. call / apply / bind로 this 직접 지정하기
function greet() {
  console.log(this.name);
}
const user = { name: "Dana" };
greet.call(user); // 👉 "Dana"
  • call, apply는 즉시 호출하며 this를 지정합니다.
  • bind는 this가 지정된 새로운 함수를 반환하며, 나중에 호출할 때 적용됩니다.

  • 전체 요약표
상황this가 참조하는 것
전역window (브라우저), {} 또는 global (Node)
일반 함수전역 or undefined (strict mode)
객체 메서드메서드를 호출한 객체
화살표 함수상위 스코프의 this
생성자 함수새로 생성된 인스턴스 객체
클래스인스턴스 객체
이벤트 핸들러 (일반 함수)이벤트 발생 대상 DOM 요소
이벤트 핸들러 (화살표 함수)상위 스코프의 this
call/apply/bind 사용지정한 객체

2.5 new 연산자와 생성자 함수

function User(name) {
  this.name = name
  this.greet = function () {
    console.log('Hello, ' + this.name)
  }
}
let user = new User('Alice')
user.greet()

2.6 옵셔널 체이닝 (?.)

let user = {}
console.log(user?.address?.street) // undefined

3. 자료구조 : 자료형

3.1 원시값의 메서드 (Primitive Values Methods)

원시값(예: string, number, boolean, null, undefined)에는 메서드가 존재하지 않지만, 자바스크립트에서 String, Number, Boolean과 같은 래퍼 객체가 자동으로 생성되어 메서드를 사용할 수 있습니다. 예를 들어 String 객체는 .toUpperCase(), .split() 등의 메서드를 제공합니다.

3.2 숫자형과 메서드

숫자형도 마찬가지로 메서드를 가질 수 있습니다:

const num = 123
console.log(num.toString()) // "123"
console.log(num.toFixed(2)) // "123.00"
console.log(typeof num) // "number" - 여전히 숫자형임

3.3 배열과 메서드

배열은 객체이므로 실제로 메서드를 가지며, 다음과 같은 주요 메서드들을 제공합니다:

const arr = [1, 2, 3]

// 변형 메서드
arr.push(4) // [1, 2, 3, 4]
arr.pop() // [1, 2, 3]

// 접근자 메서드
arr.includes(2) // true
arr.indexOf(2) // 1

// 배열 변환 메서드
arr.map((x) => x * 2) // [2, 4, 6]
arr.filter((x) => x % 2) // [1, 3]

3.4 이터러블 객체와 메서드

이터러블 객체는 순회 가능한 객체이며, 다음과 같은 메서드들을 지원합니다:

const iterable = [1, 2, 3]
for (const item of iterable) {
  console.log(item)
}

// 스프레드 연산자
const newArray = [...iterable] // [1, 2, 3]

// 구조 분해 할당
const [first, ...rest] = iterable // first: 1, rest: [2, 3]

3.5 Object.keys(), values(), entries()

객체의 키, 값, 키-값 쌍을 배열로 가져오는 메서드들입니다:

const obj = { a: 1, b: 2 }

Object.keys(obj) // ['a', 'b']
Object.values(obj) // [1, 2]
Object.entries(obj) // [['a', 1], ['b', 2]]

3.6 구조 분해 할당

배열이나 객체의 값을 변수에 쉽게 할당하는 문법입니다:

const arr = [1, 2, 3]
const [first, second, third] = arr

const obj = { name: '홍길동', age: 25 }
const { name, age } = obj

3.7 Date 객체와 날짜 처리

날짜와 시간을 다루는 객체입니다:

const date = new Date()

date.getFullYear() // 연도
date.getMonth() // 월 (0-11)
date.getDate() // 일
date.getHours() // 시
date.getMinutes() // 분
date.getSeconds() // 초

3.8 JSON과 메서드

JSON 데이터를 문자열로 변환하거나 파싱하는 메서드들입니다:

const obj = { name: '홍길동', age: 25 }

// 객체를 JSON 문자열로 변환
const jsonString = JSON.stringify(obj)
console.log(jsonString) // {"name":"홍길동","age":25}

// JSON 문자열을 객체로 파싱
const parsedObj = JSON.parse(jsonString)
console.log(parsedObj.name) // 홍길동

4. 함수 심화개념

4.1 재귀와 스택

재귀는 함수가 스스로를 호출하는 방식을 의미하며, 메모리 스택을 통해 실행됩니다.

function factorial(n) {
  // 재귀의 기본 조건
  if (n === 0) return 1

  // 재귀 호출
  return n * factorial(n - 1)
}

console.log(factorial(5)) // 120

실행 과정:

  1. factorial(5) 호출
  2. factorial(4) 호출 (스택에 5 저장)
  3. factorial(3) 호출 (스택에 4 저장)
  4. factorial(2) 호출 (스택에 3 저장)
  5. factorial(1) 호출 (스택에 2 저장)
  6. factorial(0) 호출 (스택에 1 저장)
  7. 스택에서 값들이 순차적으로 반환되어 최종 결과 계산

4.2 나머지 매개변수와 전개 구문

나머지 매개변수(...)는 여러 인수를 배열로 받을 수 있게 해주며, 전개 구문은 배열을 인자로 분리할 수 있게 합니다.

function sum(...numbers) {
  return numbers.reduce((acc, current) => acc + current, 0)
}

console.log(sum(1, 2, 3, 4)) // 10

전개 구문 예시

const numbers = [1, 2, 3]
console.log(Math.max(...numbers)) // 3

// 객체 전개
const user = { name: '홍길동' }
const admin = { ...user, role: '관리자' }
console.log(admin) // { name: '홍길동', role: '관리자' }

4.3 변수의 유효범위와 클로저

클로저는 함수가 생성될 때의 렉시컬 환경을 기억하는 함수입니다.

  1. 렉시컬 스코프 : inner 안에서도 a 에 접근가능 한 것
  2. 렉시컬 환경 : 렉시컬 스코프를 가능하게 해주는 구조
  3. 스코프 체인 : 렉시컬 환경들이 연결 된 것
function outer() {
  let a = 1;

  function inner() {
    let b = 2;
    console.log(a + b);
  }

  inner();
}
outer();
  • 클로저가 사용되는 현업 예시
  1. 데이터 은닉
function createUser(name) {
  let password = 'secret';

  return {
    getName: function () {
      return name;
    },
    getPassword: function () {
      return password;
    },
    setPassword: function (newPw) {
      password = newPw;
    },
  };
}

const user = createUser('Alice');
console.log(user.getName()); // Alice
console.log(user.getPassword()); // secret
user.setPassword('1234');
console.log(user.getPassword()); // 1234
  1. 상태 유지
function createCounter() {
  let count = 0;

  return function () {
    count++;
    return count;
  };
}

const counter = createCounter();

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
  1. 콜백 함수
for (var i = 1; i <= 3; i++) {
  (function (j) {
    setTimeout(function () {
      console.log(j);
    }, j * 1000);
  })(i);
}
  1. 이벤트 처리
function registerClickTracker(buttonId) {
  let clickCount = 0;

  document.getElementById(buttonId).addEventListener('click', function () {
    clickCount++;
    console.log(`Button clicked ${clickCount} times`);
  });
}

registerClickTracker('my-button');

4.4 오래된 var

var는 함수 스코프를 가지며, 호이스팅이 발생합니다.

호이스팅 예시

console.log(x) // undefined
var x = 10

// 함수 스코프
if (true) {
  var y = 20
}
console.log(y) // 20 (접근 가능)

4.5 전역 객체

브라우저 환경에서는 window, Node.js 환경에서는 global이 전역 객체입니다.

// 브라우저 환경
window.console.log('전역 객체')

// Node.js 환경
global.console.log('전역 객체')

4.6 객체로서의 함수

함수는 객체이므로 프로퍼티를 가질 수 있습니다.

// 함수 객체
function greet(name) {
  return `안녕하세요, ${name}!`
}
greet.description = '인사하는 함수'

4.8 setTimeout과 setInterval

비동기 실행을 위한 타이머 함수들입니다.

// 단일 실행
setTimeout(() => {
  console.log('3초 후 실행')
}, 3000)

// 주기적 실행
const intervalId = setInterval(() => {
  console.log('1초마다 실행')
}, 1000)

// 실행 취소
clearInterval(intervalId)

4.9 call/apply와 데코레이터

함수의 호출 방식을 변경하거나 기능을 확장할 수 있습니다.

// call/apply
const user = { name: '홍길동' }
function greet(age) {
  return `${this.name}${age}살입니다.`
}
console.log(greet.call(user, 25)) // 홍길동은 25살입니다.

// 데코레이터
function loggingDecorator(func) {
  return function (...args) {
    console.log('함수 호출 전')
    const result = func(...args)
    console.log('함수 호출 후')
    return result
  }
}

const add = loggingDecorator(function (a, b) {
  return a + b
})

4.10 함수 바인딩

바인딩이란 값, 변수, 함수, 데이터 등을 어떤 대상에 연결하는 것을 말합니다. 함수 내부의 this 값을 고정할 수 있습니다.

const user = {
  name: '홍길동',
  greet() {
    return `안녕하세요, ${this.name}입니다.`
  },
}

const boundGreet = user.greet.bind(user)
console.log(boundGreet()) // 안녕하세요, 홍길동입니다.

그런데 const boundGreet = user.greet.bind(user)는?

여기서 bind(user)를 해주면 this를 user 객체로 "고정한" 새 함수를 만들어줘요. 즉, boundGreet()를 어디서 호출하든 this는 항상 user! greet 함수를 다른 곳으로 전달하거나, 콜백으로 넘길 경우 this가 사라질 수 있기 때문이에요.

const say = user.greet;
console.log(say()); // ❌ undefined입니다. (this가 window 또는 undefined)

4.11 화살표 함수

기존 함수와 달리 this 바인딩이 없으며, 더 간결한 문법을 제공합니다.

// 기본 문법
const sum = (a, b) => a + b

// 객체 리터럴 반환
const createUser = (name, age) => ({ name, age })

// 인자 없이 호출
const greet = () => console.log('안녕하세요')

// 여러 줄의 코드
const calculate = (a, b) => {
  const temp = a + b
  return temp * 2
}

5. 프로토타입과 프로토타입 상속

5.1 프로토타입 상속

JavaScript에서 모든 객체는 자신의 프로토타입 객체를 가질 수 있으며, 이를 통해 상속 관계를 형성합니다. 객체는 프로토타입 체인을 통해 부모 객체의 속성과 메서드를 상속받을 수 있습니다. 이 메커니즘을 프로토타입 상속이라고 합니다.

function Animal(name) {
  this.name = name
}

Animal.prototype.speak = function () {
  console.log(this.name + ' makes a sound')
}

const dog = new Animal('Dog')
dog.speak() // Dog makes a sound

dog 객체는 Animal.prototype에서 정의된 speak 메서드를 상속받습니다. 이 방식으로 객체 간의 상속이 이루어집니다.

5.2 함수의 prototype 프로퍼티

JavaScript에서 함수는 prototype이라는 속성을 가지고 있습니다. 이 prototype 속성은 해당 함수로 생성된 객체들의 프로토타입 객체를 정의합니다. 즉, 생성자 함수로 만들어진 객체들이 상속받을 속성이나 메서드를 설정하는 역할을 합니다.

function Person(name) {
  this.name = name
}

Person.prototype.greet = function () {
  console.log('Hello, ' + this.name)
}

const person1 = new Person('Alice')
person1.greet() // Hello, Alice`

위 예시에서 Person 함수의 prototype에 greet 메서드를 추가하였고, 이를 person1 객체가 상속받아 사용하게 됩니다.

5.3 내장 객체의 프로토타입

JavaScript의 내장 객체들 역시 prototype을 가지고 있습니다. 예를 들어, Array, Object, Function 등의 객체는 각자 고유한 prototype을 통해 메서드와 속성을 공유합니다.

console.log(Array.prototype); // Array.prototype 객체
console.log(Object.prototype); // Object.prototype 객체

const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // true
위 코드에서 arr 객체는 Array.prototype을 프로토타입으로 가지고 있음을 확인할 수 있습니다.

5.4 프로토타입 메서드와 proto가 없는 객체

proto는 객체의 내부 속성으로, 객체의 프로토타입을 직접 참조할 수 있게 해줍니다. 하지만 ES6 이후로는 Object.getPrototypeOf()와 Object.setPrototypeOf()를 사용하는 것이 권장됩니다. proto 속성은 명시적으로 객체의 프로토타입을 변경하거나 확인할 때 사용됩니다.

const person = {
  name: 'John',
  greet: function () {
    console.log('Hello ' + this.name)
  },
}

console.log(person.__proto__) // Object.prototype
console.log(person.__proto__ === Object.prototype) // true

여기서 person 객체는 Object.prototype을 프로토타입으로 가집니다. proto는 이 객체의 프로토타입을 확인하는 데 사용되었습니다.

proto가 없는 객체

ES6 이후에는 Object.create(null)을 사용하여 proto가 없는 객체를 생성할 수 있습니다. 이러한 객체는 프로토타입 체인을 통해 어떤 객체도 상속받지 않습니다.

const obj = Object.create(null)
console.log(obj.__proto__) // undefined

위 코드에서 생성된 obj 객체는 프로토타입이 없으므로, proto가 undefined로 반환됩니다.

6. 클래스와 객체지향 프로그래밍

6.1 클래스와 기본 문법

클래스는 객체지향 프로그래밍에서 데이터와 그 데이터를 조작하는 함수들의 모임입니다.

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  greet() {
    console.log(`안녕하세요, 제 이름은 ${this.name}이고 ${this.age}살입니다.`)
  }
}

const person = new Person('홍길동', 25)
person.greet() // 출력: 안녕하세요, 제 이름은 홍길동이고 25살입니다.

6.2 클래스 상속

상속은 부모 클래스의 속성과 메소드를 자식 클래스가 물려받는 것입니다. 아래 예시는 Dog 이 Animal 의 메소드를 상속하고 오버라이딩 한 것입니다.

class Animal {
  constructor(name) {
    this.name = name
  }

  sound() {
    console.log('소리를 냅니다.')
  }
}

class Dog extends Animal {
  sound() {
    console.log('멍멍!')
  }
}

const dog = new Dog('댕댕이')
dog.sound() // 출력: 멍멍!

6.3 정적 메서드와 정적 프로퍼티

정적 멤버는 클래스에 직접 연결되어 있어 인스턴스를 생성하지 않고 바로 사용할 수 있습니다.

class Calculator {
  static PI = 3.14159

  static add(a, b) {
    return a + b
  }

  multiply(a, b) {
    return a * b
  }
}

console.log(Calculator.PI) // 3.14159
console.log(Calculator.add(2, 3)) // 5
const calc = new Calculator()
console.log(calc.multiply(2, 3)) // 6

6.4 private, protected 프로퍼티와 메서드

접근 제어자는 클래스의 내부 구현을 보호하고 캡슐화를 제공합니다.

class BankAccount {
  #balance = 0 // private 필드

  constructor(initialBalance) {
    this.#balance = initialBalance
  }

  deposit(amount) {
    this.#balance += amount
  }

  getBalance() {
    return this.#balance
  }
}

const account = new BankAccount(1000)
account.deposit(500)
console.log(account.getBalance()) // 1500
// account.#balance;  // SyntaxError: Private field '#balance' must be declared in an enclosing class

6.5 내장 클래스 확장하기

JavaScript의 내장 클래스를 확장하여 새로운 기능을 추가할 수 있습니다.

class CustomArray extends Array {
  sum() {
    return this.reduce((acc, curr) => acc + curr, 0)
  }

  average() {
    return this.sum() / this.length
  }
}

const numbers = new CustomArray(1, 2, 3, 4, 5)
console.log(numbers.sum()) // 15
console.log(numbers.average()) // 3

6.5 instanceof로 클래스 확인하기

instanceof 연산자는 객체가 특정 클래스의 인스턴스인지 확인합니다.

class Animal {}
class Dog extends Animal {}

const dog = new Dog()

console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
console.log(dog instanceof Object) // true

7. 프로미스와 async, await

7.1 콜백 (Callback)

  • 자바스크립트에서 비동기 작업을 처리하기 위해 가장 먼저 사용된 방법
  • 함수가 다른 함수에 인자로 전달되어 작업이 끝난 후 실행됨
  • 콜백 지옥(callback hell)이라는 단점이 있음

// 기본 콜백 함수 
function greet(name, callback) {
  console.log("안녕하세요, " + name + "님!");
  callback(); // 이 부분이 콜백 호출
}

function sayBye() {
  console.log("잘 가세요!");
}

greet("지훈", sayBye);

// 배열 메서드에서 콜백 사용 

const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(function(num) {
  return num * 2;
});

console.log(doubled); // [2, 4, 6, 8, 10]


//비동기 콜백 (setTimeout)

console.log("시작");

setTimeout(function() {
  console.log("3초 후 실행");
}, 3000);

console.log("끝");

7.2 프라미스 (Promise)

  • 비동기 작업을 더 깔끔하게 처리할 수 있도록 ES6에서 도입됨
  • 성공(resolve) 또는 실패(reject)를 나타내는 객체

Promise 객체는 resolve와 reject를 호출해서 '미래에 결과를 제공하는' 일종의 약속 객체입니다. resolve(value) → 성공 시 반환할 값 reject(error) → 실패 시 전달할 에러

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('성공!');
    // reject('실패!');
  }, 1000);
});

promise.then(result => {
  console.log(result);
}).catch(error => {
  console.error(error);
});

7.3 프라미스 체이닝 (Promise Chaining)

  • 여러 비동기 작업을 순차적으로 처리할 때 사용
fetchUser()
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(error => console.error(error));

7.4 프라미스와 에러 핸들링

  • .catch()를 통해 에러를 통합적으로 처리할 수 있음
someAsyncFunction()
  .then(result => {
    return anotherFunction(result);
  })
  .then(nextResult => {
    console.log(nextResult);
  })
  .catch(error => {
    console.error('에러 발생:', error);
  });

7.5 프라미스 API

  • Promise.all([]) : 모든 프로미스가 완료될 때까지 기다림
  • Promise.race([]) : 가장 먼저 완료되는 하나의 결과를 반환
  • Promise.allSettled([]) : 모든 프로미스가 끝날 때까지 기다리고, 각각의 결과를 반환
  • Promise.any([]) : 하나라도 성공하면 결과 반환 (실패는 무시)
Promise.all([p1, p2, p3])
  .then(results => console.log(results))
  .catch(err => console.error(err));

7.6 async / await

  • ES8에서 도입된 문법
  • 비동기 코드를 마치 동기처럼 작성 가능
async function fetchData() {
  try {
    const user = await fetchUser();
    const posts = await fetchPosts(user.id);
    const comments = await fetchComments(posts[0].id);
    console.log(comments);
  } catch (error) {
    console.error('에러 발생:', error);
  }
}

fetchData();
Previous
SQL (실습)