이 글은 이터레이션과 for... of 문, 제네레이터를 참고하여 작성된 글입니다.
🤝 이터레이션 프로토콜
이터레이션(iteration) 은 ES6에서 새롭게 추가된 프로토콜로 데이터 컬렉션을 순회하기 위한 프로토콜이다. 이터레이션 프로토콜은 이터러블 프로토콜과 이터레이터 프로토콜로 구성되어 있다.
이터레이션 프로토콜을 준수한 객체는 데이터 제공자로서 for... of 문과 spread 문법의 피연산자로 사용될 수 있다.
🤝 이터러블 프로토콜
이터러블 프로토콜을 준수한 객체를 이터러블(iterable)이라 한다. 이터러블은 Symbol.iterator 메서드를 구현하여 소유하고 있거나, 프로토타입 객체로부터 상속받았다면 이터러블 프로토콜을 준수한 객체로 이터러블이라 한다. ES6에서 제공하는 빌트인 이터러블의 대표적인 예는 아래와 같다.
Array, String, Map, Set,...
ES6에서 제공하는 빌트인 이터러블 중 배열(Array)을 살펴보자.
const arr = [1, 2, 3, 4, 5];
//iterable protocol
console.log(Symbol.iterator in arr);
//for...of
for (const elem of arr) {
console.log(elem);
}
//spread
console.log(...arr);
위와 같이 배열(Array)은 Symbol.iterator를 프로토타입 객체로부터 상속받았기 때문에 이터러블 프로토콜을 준수하고 있는 객체로 for... of문과 spread 문법의 피연산자로 사용할 수 있다.
하지만, 객체는 Symbol.iterator 메소드를 프로토타입 객체로부터 상속받지 못했기 때문에 이터러블 프로토콜을 준수하지 않는다. 즉, for... of문과 spread 문법의 피연산자로 사용할 수 없다.
🤝 이터레이터 프로토콜
이터레이터 프로토콜을 준수한 객체를 이터레이터(iterator)라 한다. 이터레이터는 next 메서드를 소유하고, next 메소드를 호출하면 이터러블을 순회하며 value, done이라는 프로퍼티를 갖는 리절트(result) 객체를 반환한다.
위에서 언급한 이터러블 프로토콜을 준수한 객체의 Symbol.iterator 메서드를 호출하면 반환하는 객체가 바로 이터레이터이다. 반환된 이터레이터는 next 메서드를 가지고 있다.
const iterable = [1, 2, 3, 4, 5];
//iterable protocol
console.log(Symbol.iterator in iterable);
//iterator
const iterator = iterable[Symbol.iterator]();
//true
console.log("next" in iterator);
이러한 iterator의 next 메서드를 호출하면 value, done을 프로퍼티로 소유한 리절트 객체를 반환한다.
console.log(iterator.next()); //{ value: 1, done: false }
모두 순회를 마친 후, next 메서드가 호출되면 아래와 같은 리절트 객체를 반환한다.
console.log(iterator.next()); // {value: undefined, done: true}
🤔 이터레이션 프로토콜을 준수하지 않는다면, 데이터 공급자로서 역할을 할 수 없는 것일까?
프로토타입 객체로부터 Symbol.iterator 메서드를 상속받았거나, 구현하였을 때 이터러블 프로토콜을 준수하며 이터러블이라 한다. 이와 같이 빌트인 이터러블 객체로부터 상속받지 못하였더라도 Symbol.iterator를 구현하면 이터러블 프로토콜을 준수할 수 있게 되며, 이러한 이터러블 객체를 커스텀 이터러블이라 한다.
💁♂️ 커스텀 이터러블
일반 객체는 이터러블이 아니다. 일반 객체에 Symbol.iterator 메서드를 구현하여 이터러블 프로토콜을 준수하게 하여 for...of , spread 문법의 피연산자로서 역할을 할 수 있게 한다.
const obj = {
[Symbol.iterator]() {
let i = 0;
const max = 5;
return {
next() {
i += 1;
return {
value: i,
done: i >= max,
};
},
};
},
};
console.log(Symbol.iterator in obj);
for (const i of obj) {
console.log(i);
}
const arr = [...obj];
console.log(arr);
const [first, second, ...rest] = obj;
console.log(first, second);
console.log(rest);
Symbol.iterator 메소드를 구현하여 소유하기 때문에 이터러블 프로토콜을 준수하며 for... of, spread 문법의 피연산자로 사용될 수 있다.
😯 이터러블을 생성하는 함수, 제네레이터
제네레이터는 이터러블 프로토콜을 준수하여 이터러블 객체를 생성하는 것보다 쉽게 이터러블을 생성하는 함수이다. 이터러블이면서, 이터레이션인 객체를 생성하는 함수를 직접 구현해보자.
const createGenerator = function () {
let i = 0;
const max = 5;
return {
[Symbol.iterator]() {
return this;
},
next() {
i += 1;
return {
value: i,
done: i >= max,
};
},
};
};
console.log(Symbol.iterator in createGenerator());
const counter = createGenerator();
console.log("next" in counter);
console.log(counter.next());
제네레이터를 사용하면 아래와 같이 구현할 수 있다.
function* generator() {
for (const i of [1, 2, 3]) {
yield i;
}
}
const counter = generator();
console.log(counter.next());
제네레이터는 위와 같이 function 키워드 뒤에 *을 붙여 선언하고, 하나 이상의 yield 문을 포함하고 있어야 한다. 제네레이터 함수를 호출하면 제네레이터 함수가 실행되는 것이 아니라 제네레이터 객체를 반환한다. 제네레이터 함수를 통해 반환된 제네레이터 객체는 next 메서드를 호출하기 위해 Symbol.iterator를 구현할 필요 없이 next를 사용할 수 있다.
function* generator() {
console.log("Step 1");
yield 1;
console.log("Step 2");
yield 2;
console.log("Step 3");
yield 3;
}
const counter = generator();
console.log(counter.next());
console.log(counter.next());
console.log(counter.next());
console.log(counter.next());
제네레이터의 next는 이터레이터의 next와 달리 인자를 전달받을 수 있다.
function* generator() {
while (true) {
const action = yield;
if (action === "HELLO") {
console.log("hello!");
} else {
console.log("bye!");
}
}
}
const counter = generator();
console.log(counter.next());
console.log(counter.next("HELLO"));
이러한 코드 진행 방식은 비동기 처리를 하는 함수를 통해 결괏값을 yield를 통해 전달받아 사용할 수 있으며 비동기 처리의 순서를 보장받을 수 있다.
const fetch = require("node-fetch");
function getTodo(generator, id) {
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then((response) => response.json())
.then((json) => generator.next(json.title));
}
const genr = (function* () {
let user;
user = yield getTodo(genr, 1);
console.log(user);
user = yield getTodo(genr, 2);
console.log(user);
user = yield getTodo(genr, 3);
console.log(user);
})();
genr.next();
'JavaScript' 카테고리의 다른 글
[JavaScript] 비동기 처리 (1) (0) | 2020.05.29 |
---|---|
[Questions about JavaScript Language Basics] Array Destructuring (0) | 2020.04.16 |
[Questions about JavaScript Language Basics] 호이스팅(Hoisting) (0) | 2020.04.13 |
[Questions about JavaScript Language Basics] JavaScript 기초 (0) | 2020.04.09 |