IMMERSIVE

ES6 정리

1. What is ES6?

  • JavaScript(JS)는 가벼운 인터프리터 또는 JIT 컴파일 프로그래밍 언어로, 일급 함수를 지원합니다. 웹 페이지의 스크립트 언어로서 제일 유명하지만 Node.js, Apache CouchDB, Adobe Acrobat처럼 많은 비 브라우저 환경에서도 사용하고 있습니다. JavaScript는 프로토타입 기반의 동적 다중 패러다임 스크립트 언어로, 객체지향형, 명령형, 선언형(함수형 프로그래밍 등) 스타일을 지원합니다. 자세한 내용은 JavaScript에 대하여를 참고하세요.

  • JavaScript의 표준은 ECMAScript입니다. 2012년 기준 최신 브라우저는 모두 ECMAScript 5.1을 온전히 지원합니다. 이전 브라우저의 경우는 최소한 ECMAScript 3까지는 지원합니다. 2015년 6월 17일 ECMA International에서는 공식명 ECMAScript2015 로 불리는 ECMAScript의 6번째 주 버전을 발표했습니다(보통 ECMAScript6 혹은 ES6으로 불립니다). 그 이후 ECMAScript 표준의 출시 주기는 1년입니다. 이 문서는 최신 초안(현재 ECMAScript 2020)에 기반을 둡니다.Javascript

  • ES6(ECMA2015)는 자바스크립트에 큰 변화를 가져온 업데이트이다. ES6 부터를 모던 자바스크립트라고 부르기도 한다. ES6가 나오면서 완전 새롭게 나타는 문법도 있고, 기존 문법에 몇가지만 추가된 경우도 있다. 변수선언, 함수, 문자열 등과 같은 경우는 큰 변화가 나타났고, 현재 많은 곳에서 사용되고 있다.

2. Variable

2.1 var, let, const

변수 선언에 사용하던 명령어는 var였다. 그러나 개발의 규모가 커지거나 다른 사람과 협업을 할 때 var는 치명적인 문제를 일으킬 수 있는데, 이미 만들어진 변수를 (의도치않게) 다시 덮어써서 기존 변수의 활용과 다르게 사용될 위험이 있다.

따라서 변수의 값이 변하지 않도록할 메커니즘이 필요했는데. 그것이 바로 const이다. const는 한번 할당된 변수의 내용이 변하는 것을 막아준다. 여기서 중요한 점은 할당된 변수의 내용이 변하는 것을 막아주다는 것인데, 즉 const로 만들어진 변수에 다른 새로운 값을 할당할 수 없다는 것을 의마한다.

// 불가능

const a = 1;
a = 2;

// 가능

cosnt a = {
  person: 'man'
};
a.person = 'woman';

let은 이전에 사용하던 var와 비슷하다.

// 가능

let a = 1;
a = 2;

변수선언에 기본적으로 사용해길 권장되는 것은 const이다. 만약 값을 변견해야 한다면 그 때 let을 사용한다.

2.2 temporal Dead Zone: 호이스팅(hoisting) and let

콘솔창을 이용해 아래 3가지 코드를 모두 작성해보자

// 1
var name = 'mike';
console.log(name);
// mike
// 2
console.log(name);
var name = "mike";
// undefined
// 3
console.log(name);
// error

첫번째 코드는 문제가 없다는 것을 알 것이다. 문제는 두 번째와 세 번째인데, 2번째 코드에서 name이 선언되기 전에 먼저 console로 값을 확인해봤다. 그 결과는 undefined가 나올것이다. 그렇다면 세 번째 코드 역시 undefined가 나와야하는 것으로 생각해 볼 수 있다.

자바스크립트는 기본적으로 코드를 위에서 아래쪽으로 실행한다. 하지만 무조건적으로 위에서 아래로 실행되지는 않는다. 자바스크립트는 var의 경우 코드 위치상 아래쪽에 위치하더라도, 코드가 실행되는 순간 가장 먼저 선언된다. 그래서 2번코드를 다시 생각해보면 아래와 같은 순서로 코드기 실행된다.

var name;
console.log(name);
name = "mike";

이러한 개념을 호이스팅 Hoisting이라고 한다. variable들이 미리 끌어올려저서 선언된다는 의미이다. 이러한 이유때문에 2번째 코드는 undefined라는 결과가 나온 것이다.호이스팅은 자바스크립트를 헤깔리게 만드는 개념 중 하나이다. 실제로는 에러가 발생해야 하는데, 에러가 발생하지 않기 때문이다. 이러한 문제를 보완하기 위해 등장한 개념이 let이다. 위쪽에서는 letvar와 비슷하다고 한 이유는 값의 재할당이 가능하기 때문이다. 하지만 let의 경우엔 호이스팅의 문제를 해결해서 등장한 개념이다.

2.3 Block Scope( {} )

let, constblock scope를 가진다 있다. Scope는 범위를 뜻한다. 변수들이 접근 가능한지 아닌지를 결정하는 범위이다. 아래 코드를 콘솔창에 입력해보고 결과를 확인해 보도록 합시다.

//  1
if (true) {
  var hello = "hi";
}

console.log(hello);
// hi
// 창 새로고침 후 실행하기
//  2
if (true) {
  const hello = "hi";
}

console.log(hello);
// hello is not defined

1의 결과에는 'hi'라는 결과가 찍히고, 2의 결과에는 hello is not defined라는 결과가 나오는 것을 확인할 수 있다. const와 let은 block scope에 속하는데, 변수가 선언된 block 안에서만 존재하게 된다.

// function scope 예시
function a() {
  var hello = "hi";
}

console.log(hello);
// hello is not defined

반면 varfunction scope를 가진다. function scope는 var는 function 제한된다는 뜻이다. 다른 함수애서 변수에 접근하는 것은 막을 수 있지만, if, for 등과 같은 자바스크립트 내장가능을 사용하면 외부에서 접근할 수 있는 문제가 발생한다.

결국 블록스코프는 변수의 접근 범위를 정해준다는 개념이다. 기억해야할 점은 내부에서 외부에 선언된 변수에는 접근할 수 있지만, 외부에서 블록 내부에 있는 변수에 접근하는 것은 불가능하다는 것이다. 자바스크립트 블록은 데이터를 안쪽에서 바깥쪽으로 찾아나가는 특징이 있기 때문이다.

if (true) {
  let a = 1;
  if (true) {
    let b = 2;
    // 내부에서 외부의 변수에 접근 가능
    console.log(a);
    console.log(b);
  }
  console.log(b); // 에러! 외부에서 내부의 변수로는 접근 불가
  // b is not defined
}

2.4 그렇다면 var는 사용하면 안되는가?

반드시 사용하면 안된다는것은 아니다. const, let이 권장된다는 말이다. 그러나 이미 만들어진 수 많은 웹사이트가 var를 사용해서 만들어진 것이 많기 때문에 var가 없어지지는 않을 것이다. 그럼에도 var는 사용자가 예상하지 못한 오류를 발생시킬 수 있기 때문에 보다 안전한 개발을 위해서는 const, let을 사용하는 것이 좋다.

3. Functions

3.1 Arrow Function intro

먼저 모양부터 살펴보면 Arrow function은 () => {} 이러한 모양을 가진 함수를 말한다. 가운데 *=>*가 있어서 arrow function이라고 부른다. 보기 좋고, 빠른 사용을 위해 만들었다고 생각하면 된다.

//  기존함수 1
function 이름(arg) {
  내용;
}

// 기존함수 2
const 이름 = function(arg) {
  내용;
};

// Arrow Function
const 이름 = arg => {
  내용;
};

간단한 사용 예시를 살펴보면 다음과 같다.

const futures = ["기본", "확률", "블랙스완"];
const good_futures = futures.map(function(item) {
  return item + "굿";
});
console.log(good_futures);
// output: ['기본굿', '확률굿', '블랙스완굿']
const good_futures_arrow = futures.map(item => {
  return item + "굿";
});
console.log(good_futures_arrow);
// output: ['기본굿', '확률굿', '블랙스완굿']

3.2 this with Arrow function

javascript에서 this 어려운 개념이다this. 기존의 함수와 Arrow function은 this가 바인딩(묶이는) 범위에서 차이를 보인다.

한가지 예시를 보자

// 1
const test1 = {
  subject: "math",
  score: 70,
  todayTest: function() {
    this.score++;
  }
};
console.log(test1);
test1.todayTest();
console.log(test1);
// 2
const test2 = {
  subject: "math",
  score: 70,
  todayTest: () => {
    this.score++;
  }
};
console.log(test2);
test2.todaytest();
console.log(test2);

위의 예제의 경우 score가 1증가한 것을 확인할 수 있고, 아래의 예제는 그대로임을 확인할 수 있다. 즉, 일반적으로 this는 자신을 포함하고 있는 바로 위의 객체에 묶이는데, Arrow function의 경우는 그렇지 않다.

3.3 Array Operation with Arrow function

MDN에 있는 array 문서들을 살펴보자MDN Array. 몇몇 구문을 살펴보면 arr.filter(callback(element[, index[, array]])[, thisArg]) callback이라는 개념이 들어가 있는 것을 확인할 수 있다. callback은 다른 코드의 인수로 넘겨주는 실행가능한 함수를 의미하는데, 쉽게 생각하면 함수에 입력 값으로 다른 함수를 넣어주는 것이라고 생각하면 된다.

callback의 자리에 arrow function을 사용할 수 있다.

const weather = ["spring", "summer", "fall", "winter"];

const summerFilterFunction = item => {
  return item === "summer";
};

const summer = weather.filter(summerFilterFunction);
console.log(summer);
// output: [summer] (filter함수의 return값은 array)

위처럼 arrow function을 변수에 넣어서 callback의 값으로 넘겨주면 된다. 하지만 새로운 변수를 선언하는 것 조차도 귀찮을 수 있다. 그럴 경우 바로 arrow function을 callback으로 넘겨주면 된다.

const weather = ["spring", "summer", "fall", "winter"];

const summer = weather.filter(item => {
  return item === "summer";
});
console.log(summer);
// output: [summer] (filter함수의 return값은 array)

3.4 Default Value

함수 default value는 함수에 넘겨줄 값을 입력하지 않았을 경우 기본적으로 들어가는 값을 의미한다.

function defaultTest(subject = "math") {
  return subject;
}

console.log(defaultTest("korean"));
// output: korean
console.log(defaultTest());
// output: math

4.Strings

4.1 template literal(팀플릿 리터럴)

``(template literal)은 여러 문자열을 조금 더 쉽게 합치기 위해 만들어진 문법이다.

// 길게쓰기
console.log(`
  안녕하세요.
  만나서 반갑습니다.
  이건 템플릿 리터릴 입니다.
`);
// output:
// 안녕하세요.
// 만나서 반갑습니다.
// 이건 템플릿 리터릴 입니다.
// 1
const greeting = "hello";
console.log(greeting + " yejin " + greeting);
// output: hello yejin hello

// 2
console.log(`${hello} yejin ${hello}`);
// output: hello yejin hello
// 함수 리턴값도 넣을 수 있다.
const sum = (a, b) => {
  return a + b;
};
console.log(`sum = ${sum(1, 2)}`);
// output: sum = 3

실제로 React에서 많이 사용되는 styled-components라는 library는 템플릿 리터럴을 사용해서 만들었습니다.

4.2 More about strings String MDN

4.2.1 includes: 문자열 안에 특정 '문자'가 포함되어 있는지 확인

const sentence = "The quick brown fox jumps over the lazy dog.";

const word = "fox";

if (sentence.includes(word)) {
  console.log(`The word "${word}" is in the sentence`);
} else {
  console.log(`The word "${word}" is not in the sentence`);
}

// expected output: "The word "fox" is in the sentence"

4.2.2 repeat(): 특정 문자를 반복

"abc".repeat(2);
// output: 'abcabc'
"abc".repeat(3);
//output: 'abcabcabc'

4.2.3startsWith(): 특정 문자로 시작하는지 판별하는 함수(startsEnd()는 반대의 경우)(return true/fasle)

var str = "To be, or not to be, that is the question.";

console.log(str.startsWith("To be")); // true
console.log(str.startsWith("not to be")); // false
console.log(str.startsWith("not to be", 10)); // true

5. Array

자바스크립트 배열에는 이미 수 많은 기능들이 있다. 자세한 기능들은 Array MDN에 소개되어 있다. 배열을 잘 사용한다는 것은 결국 배열 객체에 속한 함수들을 적절하게 사용하는 것이다. underscore에서 구현해봤던 몇몇 기능들은 이미 자바스립트 배열 기능으로 들어와 있다. 따라서 MDN에 나온 모든 기능을 외울필요는 없다. 하지만 적어도 underscore에서 구현해봤던 기능들은 자주 사용되기 때문에 반드시 익숙해질 필요가 있다. ES6라고 구분짓지말고 필요한것은 익혀둘 필요가 있다. 이 파트에선 몇몇 기능들만 추가적으로 배워보는 시간입니다.

5.1 Array

// Array.of
// Array.of() 메서드는 인자의 수나 유형에 관계없이 가변 인자를 갖는 새 Array 인스턴스를 만듭니다.

const emotion = ['sacred' ,'love', 'haapy'];
// output: ["sacred", "love", "haapy"]

const emotion2 = Array.of('sacred' ,'love', 'haapy');
// output: ["sacred", "love", "haapy"]

// Array.from
// Array.from() 메서드는 유사 배열 객체(array-like object)나반복 가능한 객체(iterable object)를 얕게 복사해새로운Array 객체를 만듭니다

console.log(Array.from('foo'));
// expected output: Array ["f", "o", "o"]

console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]

6. Destructuring(구조 분해 할당)

6.1 Destructuring이란 ?

MDN에 따르면 Destructuring은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다. 즉, 배열이나 객체 요소들의 값을 변수에 담아 사용한다는 것이다. 먼저 객체를 통해 이전의 방식과 Destructuring 방식의 비교를 통해 알아보도록 하자.

const school = {
  grade: {
    first: 600,
    second: 500,
    third: 550,
    coeducational: true,
  },
  location: {
    incheon: true,
  }
};

// second값을 얻기
// old
const second = school.grade.second;
console.log(second);
// output: 500
// Destructuring이란
const {
  grade: {
    second
  }
} = school;
console.log(second);
// output: 500

이번엔 배열을 통해 이전의 방식과 Destructuring 방식을 비교해보면

const arr = [1, 2, 3, 4, 5];
// 배열에서 첫번째 값과 두번째 값을 얻기 위한 방법
// old
const first = arr[0];
const second = arr[1];
console.log(first, second);
// output: first = 1, second = 2
// Destructuring
const [first, second] = arr;
console.log(first, second);
// output: first = 1, second = 2

다음에 배울 Rest구문과 함께 쓴다면 할당 안된 나머지 요소를 하나의 변수에 할당할 수 있다.

// 배열

const arrRest = [ 1, 2, 3, 4, 5 ];
const [firstArr, ...andSoOnArray] = arrRest;
console.log(firstArr);
// output: 1
console.log(andSoOnArray);
// output: [2, 3 ,4 ,5]

// 객체

const objRest = {a: 1, b: 2, c: 3, d: 4};
const { firstObj, ...andSoOnObj} = objRest;
console.log(firstObj);
// output: 1
console.log(andSoOnObj);
// output: {b: 2, c: 3, d: 4}

6.2 이름 재설정

Destructuring해서 값을 받아올 때 상황에 맞게 이름을 바꿔 사용할 수 있다. Destructuring을 할 변수 뒤에 새로 지정할 이름을 Destructuring할당변수: newName 설정해주면 새로 설정한 변수명으로 값이 할당된다.

const school = {
  grade: {
    first: 600,
    second: 500,
    third: 550,
    coeducational: true,
  },
  location: {
    incheon: true,
  }
};

const {
  grade: {
    second: two
  }
} = school;

console.log(two);
// output: 500

6.3 함수에서 Destructuring

함수 값을 받을 때도 Destructuring을 사용할 수 있다.

function koreaSoccer(a) {
  console.log(fw);
  console.log(mf);
  console.log(df);
  console.log(gk);
}

const player = {
  fw: {
    hwang: "france",
    son: "england",
  },
  mf: {
    ki: "england",
    baik: "germany"
  },
  df: {
    kim: "china"
  },
  gk: {
    cho: "korea"
  },
};

koreaSoccer(player);
// output:{hwang: "france", son: "england"}
// output:{ki: "england", baik: "germany"}
// output:{kim: "china"}
// output:{cho: "korea"}

6.4 객체 이름 생략

만약 객체의 key와 value로 들어가는 값의 이름이 같다면, value의 이름을 생략해 줘도 된다. 예제 코드로 확인하면 바로 이해가 가능하다

const one = 1;
const obj1 = {
  one: one,
};
console.log(obj1.one);
// output: 1
const obj2 = {
  one,
};
console.log(obj2.one);
// output: 1

보는것처럼 key와 할당되는 value의 이름이 같다면, :value의 입력을 생략해도 된다.

7. Rest and Spread(전개구문)

7.1 Spread

Spread는 변수를 가져와 풀어서 헤쳐서(unpack) 전달하는 것이다. *...*로 표한한다.

const numbers = [1,2,3,4,5];
console.log(numbers)
// outout: [1, 2, 3, 4, 5]
console.log(...numbers)
// outout: 1 2 3 4 5

Spread를 사용하면 두개의 배열이나 객체를 쉽게 합칠 수 있다.

// 배열
const arr1 = [1,2,3];
const arr2 = [4,5,6];

// test1
const newArr1 = arr1 + arr2;
// output: "1,2,34,5,6"

// test2
const newArr2 = [arr1 + arr2];
// output: ["1,2,34,5,6"]

// test3
const newArr3 = [...arr1, ...arr2];
// output: [1, 2, 3, 4, 5, 6];

// test4
const newArr4 = arr1.concat(arr2);
// output: [1, 2, 3, 4, 5, 6];

// 객체
const obj1 = {
  a: 1,
  b: 2,
};

const obj2 = {
  c: 3,
  d: 4.
}

const newObj1 = {
  obj1,
  obj2
}
console.log(newObj2);
// output:
// obj1: {a: 1, b: 2}
// obj2: {c: 3, d: 4}

const newObj2 = {
  ...obj1,
  ...obj2
}
console.log(newObj2);
// output: {a: 1, b: 2, c: 3, d: 4}

7.2 Spread 사용예제

새로운 요소 추가하기

// 배열
const nations = ['korea', 'japan', 'china'];
const newNations = [...nation, 'vietnam'];

// 객체
const korea = {
  name: 'korea',
  location: 'asia',
}
const newKorea = { ...korea, north: false };
console.log(newKorea);
// output:{name: "korea", location: "asia", north: false}

7.3 Rest

rest parameter는 함수에서 수 많은 파라미터(매개변수)를 사용해야할 때 유용하다. spread에서는 뭉쳐있는 값들을 펼쳐주는 개념이라면, rest는 펼쳐져있는 값들을 하나로 뭉쳐주는 개념이다. MDN에는 이렇게 *Rest 파라미터 구문은 정해지지 않은 수(an indefinite number, 부정수) 인수를 배열로 나타낼 수 있게 합니다.*이라고 설명되어 있다.

// rest라는 이름말고 아무 이름으로해도 상관없다.
const infinitParameters = (...rest) => {
  console.log(rest);
}

infinitParameters(1,2,3,4,5,6,7,8,9,10,11,[1,2,3,4,5,6,7,8,90]);
// output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, Array(9)];

8 For ...of loop

for...of 문은 각각의 고유한 특성의 값을 실행할 명령과 함께 사용자 지정 반복 후크를 호출하여, 반복 가능한 객체(배열, Map, Set, 인수 객체 등을 포함)를 통해 반복하는 루프를 만든다.(MDN). for ...of는 배열의 값을 바로 가져오기 때문에 배열 반복문에서 흔히 사용하는 인덱스값을 사용해서 값을 불러오는 방식을 사용하지 않아도 된다.

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

// basic for
for (let i = 0; i < numbers.length; i += 1) {
  console.log(numbers[i])
}
// output: 1,2,3,4,5

// forEach
numbers.forEach((number) => { console.log(number) });
// output: 1,2,3,4,5

// for in
for (let i in numbers) {
  console.log(i);
  console.log(numbers[i]);
}
// output: 0,1,2,3,4
// output: 1,2,3,4,5

// for of
for (let i of numbers) {
  console.log(i);
}
// output: 1,2,3,4,5

for...of에는 하나의 기능이 더 있는데 바로 break이다. for...of는 반복 중에 break를 만나면 만난 자리에서 반복을 끝내고 다음 코드를 읽기 시작한다.

const numbers = [ 1,2,3,4,5,5,6,7 ];
for (let number of numbers) {
  if (number === 5) {
    break;
  } else {
    console.log(number);
  }
}
console.log('end');
// output: 1,2,3,4, "end"

9. Promises

9.1 동기적(synchronous) vs 비동기적(asynchronous)

한 가지 예시를 통해 동기적과 비동기적이라는 개념을 이해해보자. 오늘 나의 계획은 빨래를 하고 씻고 밖으로 나가는 것이다. 이 상황의 과적을 동기적 / 비동기적으로 표현해 보도록 하겠다.

1. 동기적: 
빨래를 세탁기에 넣는다 => 
빨래가 다 될때까지 기다린다 => 
빨래가 다 된 것을 확인하면 씻는다 => 
씻고 난 뒤 나간다

2. 비동기적:
빨래를 세탁기에 넣는다 =>
세탁기가 돌아가는 중에 씻는다 =>
씻고 난 뒤 빨래가 다 된것을 확인한다 =>
나 간다.

즉, 동기적인 방식은 하나의 업무의 처리 과정을 기다렸다가 다음 업무를 진행하는 것을 말한다. 비동기적인 방식은 업무를 병렬적으로 처리하는 것을 말하는데, 하나의 업무가 진행되는 중 처리할 수 있는 다른 업무가 있으면 같이 처리하는 것을 말한다.

sync&async

동기, 비동기의 개념과 함께 자바스크립트에 대해 알아야 할 것이 있다. 자바스크립트는 싱글 스레드 기반으로 프로세스를 처리한다. 즉, 작업 처리를 한 곳에서 한다고 생각하면 된다. 따라서 여러 작업이 몰려서 들어올 경우 작업이 들어온 순서에 맞게 차례로 작업을 진행한다. 순서에 맞게 진행된다는 장점이 있지만, 여러 가지의 일을 동시에 처리할 수 없는 단점이 있다. 이런 처리 기반을 가지고 있기 때문에 자바스크립트는 기본적으로 동기적 실행방식이다.

이러한 순차적 실행은 한가지 문제점을 만들어내는데, 만약 자바스크립트 코드 중 불러오는데 일정 시간이 걸리는 코드가 있다거 해보자. 예를들어

let a;

function load() {
  setTimeout(() => {
    a = 'load data';
  }, 3000);
}

load();
console.log(a); // undefined

*setTimeout(() => { 코드 }, time)*는 일정 시간이 지난 뒤 코드를 실행하는 함수다. 시간은 ms(밀리세컨드, 1/1000) 단위로 측정되기 때문에 1초뒤에 코드를 실행하려면 1000이라고 입력해주면 된다. 위의 코드는 3초뒤에 a의 값으로 load data를 넣어주는 함수이다. 함수를 실행한 뒤 바로 *console.log(a)*를 실행시켜주면 undfined가 나오게 되는데, 이유는 load함수 내부에 setTimeout으로 일정시간 딜레이를 만들어 주었기 때문이다. 순차적으로 진행되는 자바스크립트는 이러한 로드를 기다려주지 않고 바로 다름 코드를 실행시키기 때문에 *console.log(a)*의 결과는 undefined가 된다. 하지만 3초가 지난 뒤 console.log(a) 실행하면 load data가 콘솔로 출력되는 것을 확인할 수 있다.

이러한 동기적 처리 방식의 단점(순차적 진행으로 값이 오는 것들 기다려주지 않는)을 해결하기 위해 사람들은 비동기적 처리 방식을 생각해냈다. 그렇게 탄생한 방법이 먼저 콜백Callback을 사용하는 방식이다. 콜백Callback이란 쉽게 생각하면 함수의 인자값으로 나중에 실행할 함수를 넘겨주는 것이다. 이러한 콜백을 통해 동기적 처리 작업에서 나중에 실행될 함수를 넘겨줌으로 순차적으로 진행되는 중간 다른 함수의 실행을 가능하게 한 것이다.


let a;

function load(cb) {
  setTimeout(() => {
    a = 'load data';
    cb(a);
  }, 3000);
}

function print(word) {
  console.log(word);
};

load(print);
// 3초 뒤 load data 출력

하지만 이러한 방법의 문제점은 콜백지옥이 생긴다는 것이다.

// 콜백지옥
step1(function (value1) {
    step2(function (value2) {
        step3(function (value3) {
            step4(function (value4) {
                step5(function (value5) {
                    step6(function (value6) {
                        // Do something with value6
                    });
                });
            });
        });
    });
});

이러한 콜백지옥을 해결하기 위해 만들어 진 것이 Promise, async / await라고 생각하면 된다. 이 부분(콜백지옥, Promise, async/awat)은 추후 자료구조 이후에 다시 자세히 배워보도록 하자.

9.2 Promise(MDN)

Promise는 자바스크립트 비동기 처리에 사용되는 객체이다. 프로미스가 생성될 때 꼭 알 수 있지는 않은 값을 위한 대리자로, 비동기 연산이 종료된 이후의 결과값이나 실패 이유를 처리하기 위한 처리기를 연결할 수 있도록 합니다. 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다. 다만 최종 결과를 반환하지는 않고, 대신 프로미스를 반환해서 미래의 어떤 시점에 결과를 제공합니다.

Promise는 다음 중 하나의 상태를 가집니다.

  • 대기(pending): 이행하거나 거부되지 않은 초기 상태.
  • 이행(fulfilled): 연산이 성공적으로 완료됨.
  • 거부(rejected): 연산이 실패함.

대기 중인 프로미스는 값과 함께 이행할 수도, 어떤 이유(오류)로 인해 거부될 수 있습니다. 이행이나 거부될 때, 프로미스에 연결한 처리기는 그 프로미스의 then 메서드에 의해 대기열에 오릅니다. 이미 이행했거나 거부된 프로미스에 연결한 처리기도 호출하므로, 비동기 연산과 처리기 연결 사이에 경합 조건race condition은 없습니다. 프로미스의 핵심은 내가 아직 모르는 value와 함께 작업할 수 있게 해준다는 것이다.

promise

// 프로미스의 모습
const prom = new Promise((resolve, reject) => {
  // promise를 resolve or reject
  // resolve => 에러가 없을 경우 넘겨줄 데이터
  // reject => 에러가 있을 경우 넘겨줄 데이터
  if(dont error) {
    resolve(successData);
  } else {
    reject(errorData);
  }
});

1) 대기(Pending)

new Promise() 메서드를 호출하면 대기Pending 상태가 된다.

new Promise()

*new Promise()*메서드를 호출할 때 콜백 함수의 인자로 resolve, reject에 접근할 수 있다.

new Promise((resolve, reject) => {
  // resolve
  // reject
})

2) 이행(fulfilled)

여기서 콜백 함수의 인자 resolve를 실행하면 이행(fulfilled) 상태가 된다.

new Promise((resolve, reject) => {
  resolve();
})

그리고 이행 상태가 되면 then() 함수를 용해 처리 결과 값을 받을 수 있다.

function getData() {
  return new Promise((resolve, reject) => {
    let data = 100;
    resolve(data);
  })
}

getData().then((resolvedData) => {
  console.log(resolvedData) // 100
});

3) 거부(rejected)

reject인자로 reject() 메서드를 실행하면 Rejected 상태가 된다.

new Promise((resolve, reject) => {
  reject();
})

실패 상태가 되면 실패 원인을 catch() 함수를 통해 받을 수 있다.

function getData() {
  return new Promise((resolve, reject) => {
    reject(new Error("loading failed"))
  })
}

getData().then().catch((error) => {
  console.log(error) // loading failed
});

더 자세한 내용은 Promise MDN을 참고하면 된다.

10. Async / Await

Async / Await는 Promise를 사용하는 코드를 더 보기 좋은 코드로 만들기 위함이다. async function 선언은 AsyncFunction 객체를 반환하는 하나의 비동기 함수를 정의한다. (AsyncFunction객체는 async function을 만드는 객체이다.) 비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise를 사용하여 결과를 반환한다. 그러나 비동기 함수를 사용하는 코드의 구문과 구조는 표준 동기 함수를 사용하는 것과 많이 비슷하다.

10.1 구조

async function name([param[, param[, ... param]]]) { 
    statements
}

// name: 함수이름
// param: 함수에 전달되기 위한 인자의 이름
// statements: 함수본문을 구성하는 내용
// return: Promise. async 함수에 의해 반환 된 값으로 해결되거나 async 함수 내에서 발생하는 캐치되지 않는 예외로 가부되는 값(resolve, reject)

10.2 await

async함수에는 await식이 포함될 수 있다. 이 식은 async 함수의 실행을 일시 중지하고 전달된 Promise의 해결을 기다린 다음 async 함수의 실행을 다시 시작하고 완료후 값을 반환한다. await 키워드는 async 함수에서만 유효하다. async 함수의 본문 외부에서 사용하면 에러가 발생한다.

async/await함수의 목적은 사용하는 여러 promise의 동작을 동기스럽게 사용할 수 있게 하고, 어떠한 동작을 여러 promise의 그룹에서 간단하게 동작하게 하는 것이다.

10.3 Example

var resolveAfter2Seconds = function() {
  console.log("starting slow promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve(20);
      console.log("slow promise is done");
    }, 2000);
  });
};

var resolveAfter1Second = function() {
  console.log("starting fast promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve(10);
      console.log("fast promise is done");
    }, 1000);
  });
};

var sequentialStart = async function() {
  console.log('==SEQUENTIAL START==');

  // If the value of the expression following the await operator is not a Promise, it's converted to a resolved Promise.
  const slow = await resolveAfter2Seconds();
  console.log(slow);

  const fast = await resolveAfter1Second();
  console.log(fast);
}

sequentialStart();
// output
// ==SEQUENTIAL START==
// starting slow promise
// slow promise is done
// 20
// starting fast promise
// fast promise is done
// 10
}

11. Class

*ES6(ECMAScript 2015)*는 2015년 만들어진 자바스크립트의 표준 문법 스펙입니다. 그 다음에 나온 ES7, ES8 등이 있지만 Class가 포함된 것은 ES6 입니다. Class가 나오기 이전의 자바스크립트에서는 클래스 대신 4가지 instantiation pattern을 이용해 클래스를 구현해 냈습니다. 그 과정이 번거로웠는지 ES6에서 Class라는 함수가 나오게 됐습니다.

생활코딩 객체지향

MDN Class

ES6 이전의 클래스 구현방식

Functional, Functional-shared, Prototypal, Pseudoclassical의 4가지 방식 중 가장 많이 쓰였던 Pseudoclassical로 클래스를 구현해 봅시다.

var Car = function(name, speed) {
  this.name = name;
  this.speed = speed;
  this.position = 0;
};

Car.prototype.move = function() {
  this.position += this.speed;
};

var car = new Car('My car', 5);
console.log(car.position);
car.move();
console.log(car.position);

위와 같이 작성하며, console.log에 의해 0, 5라는 값이 찍힐 것 입니다. 사실 이렇게 사용해도 문제는 없지만 더 원활한 사용을 위해 클래스가 탄생했습니다.

ES6 이후의 클래스 구현방식

위에서 만들었던 Car 클래스를 ES6 방식으로 구현하면 다음과 같습니다.

class Car {
  constructor(name, speed) {
    this.name = name;
    this.speed = speed;
    this.position = 0;
  }

  move() {
    this.position += this.speed;
  }
}

const car = new Car('My car', 5);
console.log(car.position);
car.move();
console.log(car.position);

위와 같이 작성할 수 있으며, 이번에도 마찬가지로 console.log에 의해 0, 5라는 값이 찍히게 됩니다. 먼저, 한눈에 Class라는 것을 알 수 있고, 그 내부에 메소드를 작성하기 때문에 가독성이 좋습니다.

기본적인 구조를 한번 살펴보겠습니다.

class 클래스이름 {
  constructor(속성들) {
    this.속성1 = 속성1;
    this.속성2 = 속성2;
    ...
  }

  메소드() {
    ...
  }
}

먼저 constructor 메소드로 클래스를 초기화 해줍니다. 그리고 그 아래에 다른 메소드들을 작성합니다. 정말 간단하죠?

Subclass

사실 클래스 선언만 한다면 ES6 이전이나 이후나 많이 다른 부분은 없습니다. 하지만, 상속을 받게 된다면 말이 달라지죠. 자식 클래스를 만드는 방법을 ES6 이전과 이후로 비교해 봅시다.

ES6 이전

var SuperCar = function(name, speed) {
  Car.call(this, name, speed);
};

SuperCar.prototype = Object.create(Car.prototype);
SuperCar.prototype.constructor = Car;

SuperCar.prototype.move = function() {
  this.position += this.speed * 2;
};

ES6 이후

class SuperCar extends Car {
  constructor(name, speed) {
    super(name, speed);
  }

  move() {
    this.position += this.speed * 2;
  }
}

위의 예제를 보면 알 수 있듯이, ES6의 클래스가 명료하고 간단합니다. extends 키워드를 통해 부모를 정할 수 있고, constructor 내부에서 super함수로 부모의 생성자를 호출할 수 있습니다. 그러면 모든 상속이 끝나게 되죠.

앞으로도 Class를 사용할 일이 많으니 확실히 숙지해 두도록 합시다.

13. 참고자료