자바스크립트 정리

노요셉·2020년 5월 31일
0

프로미스

자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백함수를 사용함.

그러나, 에러의 예외처리 곤란, 코드 깊이가 길어지는 callback hell 문제 등을 해결할

프로미스가 도입됌.

비동기 처리란?

실행 완료를 기다리지 않고, 즉시 다음 태스크를 실행한다. 따라서 비동기 함수( 비동기를 처리하는 함수) 내에서 처리하는 결과를 반환(또는 전역 변수에 할당)하면 기대한 대로 동작하지 않죠.

<script>
  function get(url) {
  	const xhr = XMLHttpRequest();
  
  	// 서버 응답시 호출될 이벤트 핸들러
  	xhr.onreadystatechange = function () {
      	// 서버 응답 완료
      	if( xhr.readyState === XMLHttpRequest.DONE) {
          if( xhr.status === 200) {
            // 정상 응답
            
            /**
             * 비동기식 처리모델은 처리 완료를 기다리지 않고 즉시 다음 태스크를 실행한다.
             * 따라서 비동기 함수 내에서 처리 결과를 반환하면 기대한 대로 동작하지 않음
             * 비동기 함수의 처리 결과에 대한 처리는 함수 내에서 처리해야 함.
             */
            return xhr.response;
          } else {
          	// 비정상 응답
            console.log('Error: ' + xhr.status);
          }
        }
    };
 
  	xhr.open('GET', url);
  	xhr.send();
}
const url = '';
/**
 * get 함수는 비동기 함수 이므로 처리완료를 기다리지 않고 즉시 다음 태스크를 수행함.
 * 즉, 함수의 실행이 완료하여 함수의 반환값을 받기 이전에 다음 태스크로 진행함.
 * 따라서 res. undefined.
 */
const res = get(url);
console.log(res); // undefined
</script>

비동기 처리함수의 처리결과를 반환하는 경우, 순서가 보장되지 않기 때문에 그 반환 결과를 가지고 후속 처리를 할 수 없다. 즉, 비동기 함수의 처리 결과에 대한 처리는 비동기 함수의 콜백 함수 내에서 처리해야 한다.

만일 비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩되어 복잡도가 높아지는 현상을 발생함 -> 콜백 헬 -> 가독성 최악

예외처리 한계

try {
	setTimeout(() => { throw 'Error!'; }, 1000);
} else {
  console.log('에러를 캐치하지 못함', e);
}

try 블록 내에서 setTimeout함수가 실행되면 1초 후에 콜백 함수가 실행되고 이 콜백 함수는 예외를 발생시킨다. 하지만 이 예외는 catch 블록에서 캐치되지 않는다.

그 이유는 비동기 처리 함수의 콜백 함수는 해당 이벤트(DOM, timer 함수의 tick이벤트, Ajax의 onreadystatechange) 이벤트가 발생하면 이벤트 큐로 이동한 후 호출스택이 비워졌을 때, 호출 스택으로 이동되어 실행된다.
http://sculove.github.io/blog/2018/01/18/javascriptflow/

setTimeout함수는 비동기 함수이므로 콜백 함수의 실행 완료를 기다리지 않고 즉시 종료되어 호출 스택에서 제거된다. 이후 tick 이벤트가 발생하면 setTimeout 함수의 콜백 함수는 이벤트 큐로 이동한 후 호출 스택이 비워졌을 때 호출 스택으로 이동되어 실행된다.

이때 setTimeout 함수는 이미 호출 스택에서 제거된 상태에요. 이는 setTimeout 함수의 콜백 함수를 호출한 것은 setTimeout 함수가 아니라는 것을 의미해요. (this도 달라지는 이유도 이 때문이겠죠.)
setTimeout 함수의 콜백함수의 호출자가caller가 setTimeout함수라면 호출 스택에 setTimeout 함수가 존재해야 하기 때문입니다.

예외는 호출자caller 방향으로 전파됩니다. 하지만 위에서 살펴본 바와 같이 setTimeout함수의 콜백함수를 호출한 것은 setTimeout 함수가 아니에요. 따라서 setTimeout 함수의 콜백 함수 내에서 발생시킨 에러는 catch 캐치블록에서 캐치되지 않아 프로세스는 종료됩니다.

이러한 문제를 극복하기 위해서 프로미스가 제안되었대요.

프로미스 생성

const promise = new Promise((resolve, reject) => {
  // 비동기 작업을 수행함.
  if( /* 비동기 작업 수행 성공 */) {
     resolve(result);
	} else {
     reject('failure reason');
	}
});

비동기 처리 상태 구분
pending, fulfiled, rejected, setteld

pending: 비동기 처리가 아직 수행되지 않은 상태
fulfilled: 비동기 처리가 성공한 상태
rejected: 비동기 처리가 실패한 상태
settled: 비동기 처리가 수행된 상태 ( 성공 혼은 실패 )

프로미스의 사용

Promise로 구현된 비동기 함수는 Promise 객체를 반환하여야 한다. Promise로 구현된 비동기 함수를 호출하는 쪽(promise consumer)에서는 Promise 객체의 후속 처리 메서드(then, catch)를 통해 비동기 처리 결과 또는 에러 메시지를 전달받아 처리함.

프로미스 체이닝

비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩되어 복잡도가 높아지는 콜백 헬이 발생함.

이터레이션 프로토콜과 for of 루프

이터러블: 순회 가능한 자료구조. Symbol.iterator를 프로퍼티 키로 사용한 메서드를 구현하는 것에 의해 순회 가능한 자료구조인 이터러블이 됌.
이터레이터 : Symbol.iterator를 프로퍼티로 사용하는 메서드는 이터레이터를 반환한다. 이터레이터는 순회 가능한 자료구조인 이터러블의 요소를 탐색하기 위한 포인터로서 next() 메서드를 갖는 객체.

next() 메서드는 value,done 프로퍼티를 갖는 객체를 반환하며, 이 메서드를 통해 이터러블 객체를 순회할 수 있다.

https://poiemaweb.com/es6-iteration-for-of

const array = [1, 2, 3];

// 배열은 Symbol.iterator 메소드를 소유한다.
// 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다.
console.log(Symbol.iterator in array); // true

// 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for (const item of array) {
  console.log(item);
}

일반 객체는 Symbol.iterator 메소드를 소유하지 않는다. 따라서 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.

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

// 일반 객체는 Symbol.iterator 메소드를 소유하지 않는다.
// 따라서 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.
console.log(Symbol.iterator in obj); // false

// 이터러블이 아닌 일반 객체는 for...of 문에서 순회할 수 없다.
// TypeError: obj is not iterable
for (const p of obj) {
  console.log(p);
}

일반 객체는 이터레이션 프로토콜을 준수(Symbol.iterator 메소드를 소유)하지 않기 때문에 이터러블이 아니다. 따라서 일반 객체는 for…of 문에서 순회할 수 없으며 Spread 문법의 대상으로 사용할 수도 없다. 하지만 일반 객체도 이터러블 프로토콜을 준수하도록 구현하면 이터러블이 된다.

빌트인 이터러블

// 배열은 이터러블이다.
const array = [1, 2, 3];

// 이터러블은 Symbol.iterator 메소드를 소유한다.
// Symbol.iterator 메소드는 이터레이터를 반환한다.
let iter = array[Symbol.iterator]();

// 이터레이터는 next 메소드를 소유한다.
// next 메소드는 이터레이터 리절트 객체를 반환한다.
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: undefined, done: true}

// 이터러블은 for...of 문으로 순회 가능하다.
for (const item of array) {
  console.log(item);
}

// 문자열은 이터러블이다.
const string = 'hi';

// 이터러블은 Symbol.iterator 메소드를 소유한다.
// Symbol.iterator 메소드는 이터레이터를 반환한다.
iter = string[Symbol.iterator]();

// 이터레이터는 next 메소드를 소유한다.
// next 메소드는 이터레이터 리절트 객체를 반환한다.
console.log(iter.next()); // {value: "h", done: false}
console.log(iter.next()); // {value: "i", done: false}
console.log(iter.next()); // {value: undefined, done: true}

// 이터러블은 for...of 문으로 순회 가능하다.
for (const letter of string) {
  console.log(letter);
}

// arguments 객체는 이터러블이다.
(function () {
  // 이터러블은 Symbol.iterator 메소드를 소유한다.
  // Symbol.iterator 메소드는 이터레이터를 반환한다.
  iter = arguments[Symbol.iterator]();

  // 이터레이터는 next 메소드를 소유한다.
  // next 메소드는 이터레이터 리절트 객체를 반환한다.
  console.log(iter.next()); // {value: 1, done: false}
  console.log(iter.next()); // {value: 2, done: false}
  console.log(iter.next()); // {value: undefined, done: true}

  // 이터러블은 for...of 문으로 순회 가능하다.
  for (const arg of arguments) {
    console.log(arg);
  }
}(1, 2));

이터러블 계속 볼것
https://poiemaweb.com/es6-iteration-for-of

profile
서로 아는 것들을 공유해요~

0개의 댓글