내부 요소들을 공개적으로 탐색(반복)할 수 있는 데이터 구조
[Symbol.iterator] 메소드를 가지고 있음.
Array, Set, Map, String
[Symbol.iterator] 메소드가 존재하는 개체는 iterable
generator를 호출한 결과
function* generator() {
yield 1
yield 2
yield 3
}
const gen = generator();
const arr = [1, 2, 3]
const map = new Map([ [1,1], ['2',2] ])
const set = new Set([1, 2 ,3])
const str = 'string도 iterable'
const gen = (function* () {
yield 1
yield 2
yield 3
})();
1. Array.from() 메소드로 배열로 전환 가능
Array.from(arr)
Array.from(map)
Array.from(set)
Array.from(str)
Array.from(gen)
2. spread operator로 배열 전환 가능
const arr1 = [...arr]
const arr2 = [...map]
const arr3 = [...set]
const arr4 = [...str]
const arr5 = [...gen]
3. 해체 할당 가능
원하는 위치의 요소 값을 가져올 수 있음.
const [a, ,c] = arr
const [ ,mapB] = map
...
4. for ...of문 수행 가능
for(let x of str){
console.log(x) // s t r i n g ...
}
for(let x of set){
console.log(x) // 1 2 3
}
5. Promise.all , Promise.race 명령 수행 가능
6. generator - yield* 문법으로 이용 가능
const makeGenerator = iterable => function* () {
yield* iterable
// yield*: iterable 안의 요소들을 모두 펼처서 각각을 yield로 만듦
// yield 1;
// yield 2;
// yield 3;
}
const aGen = makeGenerator(arr)();
console.log(aGen.next()) // { value: 1, done: false }
console.log(aGen.next()) // { value: 2, done: false }
console.log(aGen.next()) // { value: 3, done: false }
원리
[Symbol.iterator] 메소드를 가진 개체에 대해
내부적으로 Symbol.iterator or generator 를 실행하여 iterator로 변환한 상태에서
next()를 반복 호출하는 동일한 로직을 기반으로 함.
const iterator = iterable한 개체[Symbol.iterator]()
const iterator = set[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
위 와같이 iterator.next() 메소드를 수행하면서
Array.from(), spread operator, for ...of 등을 수행합니다.
[Symbol.iterator] 메소드를 가지고 있다면 그 개체는 iterable하다
그래서 Symbol.iterator 없는 개체에 Symbol.iterator를 조건에 맞게 생성해주면
iterable할 수 있다.
new Map() // new Set() // new WeakMap() // new WeakSet()
Promise.all() // Promise.race() // Array.from()
WeakMap과 WeakSet은 iterable하지는 않지만 iterable한 개체를 인자로 받을 수 있음.
반복을 위해 설계된 특별한 인터페이스를 가진 객체
간단한 iterator 예시 1
const iter = {
items: [10, 20, 30],
count: 0,
next () {
const done = this.count >= this.items.length
return {
done,
value: !done ? this.items[this.count++] : undefined
}
}
}
console.log(iter.next()) // { done: false, value: 10 }
console.log(iter.next()) // { done: false, value: 20 }
console.log(iter.next()) // { done: false, value: 30 }
console.log(iter.next()) // { done: true, value: undefined }
간단한 iterator 예시 2
const iter = {
value: 0,
next() {
const done = ++this.value >= 5
return {
done,
value: !done ? this.value : undefined
}
}
}
기본 iterator에 접근
const str = '배고파'
const strIterator = str[Symbol.iterator]()
console.log(strIterator.next()) // { value: '배', done: false }
console.log(strIterator.next()) // { value: '고', done: false }
console.log(strIterator.next()) // { value: '파', done: false }
console.log(strIterator.next()) // { value: undefined, done: true }
객체가 iterable 한지 확인
객체가 [Symbol.iterator]를 가졌는지 확인해주면 됨.
const isIterable = target => !!target[Symbol.iterator]
console.log(isIterable({})) // false
console.log(isIterable([])) // true
console.log(isIterable('str')) // true
obj는 [Symbol.iterator] 메소드가 없기때문에 iterable 하지 않다.
[Symbol.iterator]는
done과 value 프로퍼티를 가진 next() 메소드를 포함한
iterator 객체를 반환하는 메소드이다.
obj에 [Symbol.iterator] 메소드를 추가해주면 iterable 하게 된다.
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
[Symbol.iterator]() {
let count = 0;
const items = Object.entries(this); // [[key:value],[]] 형태로 반환
return {
next() {
return {
done: count >= items.length,
value: items[count++]
};
}
};
}
};
console.log(...obj); // [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ] [ 'd', 4 ]
덕 타이핑(Duck Typing)
객체가 어떤 타입에 걸맞는 변수와 메소드를 지니면 객체를 해당 타입에 속하는 것으로 간주한다.
function* gene() {
console.log(1)
yield 1 // yield 뒤의 값을 넘겨줌.
console.log(2)
yield 22
console.log(3)
}
const gen = gene()
gen.next() // {value: 1, done: false}
gen.next() // {value: 22, done: false}
gen.next() // value: undefined, done: true}
function* gene() { yield } // 함수 선언식
const gen = function * (){ yield } // 함수 표현식
const obj = {
gene1: function* () {},
*gene2 () { yield }
}
class A {
*gene () { yield }
}
function* gene() {
console.log(1)
yield 1
console.log(2)
yield 2
console.log(3)
}
const gen = gene()
console.log(...gen)
// 1
// 2
// 3
// 1 2
객체 안의 Symbol.iterator를 제너레이터로 만들면 더 쉽게 이터레이터 생성이 가능하다.
이터레이터를 만들기 위한 조건들을 제너레이터로 표현하면 신경써줘야할 부분은 yield 뿐이다.
const obj = {
a: 1,
b: 2,
c: 3,
*[Symbol.iterator] () {
for(let prop in this){
yield [prop, this[q]]
}
}
}
console.log(...obj) // [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]
// obj는 iterable하다.
// for...of 문도 사용 가능
yield* [iterable 개체] 개체 안의 내용을 풀어줌.
function* gene() {
yield* [1,2],
yield
yield* 'ab'
}
const gen = gene()
gen.next() // {value: 1, done: false}
gen.next() // {value: 2, done: false}
gen.next() // {value: undefined, done: false}
gen.next() // {value: a, done: false}
...
function* gene1 () {
yield [1, 10]
yield [2, 20]
}
function* gene2 () {
yield [3, 30]
yield [4, 40]
}
function* gene3 () {
yield* gene1()
// yield [1, 10]
// yield [2, 20]
yield* gene2()
// yield [3, 30]
// yield [4, 40]
yield* [[5, 50], [6, 60]]
// yield [5, 50]
// yield [6, 60]
yield [7, 70]
}
const gen = gene3()
gen.next().value // [1, 10]
...
function* gene() {
let first = yield 1
let second = yield first + 2
yield second + 3
}
const gen = gene()
gen.next().value // 1
gen.next().value // NaN
gen.next().value // NaN
첫 gen.next().value
를 실행하면 let first = yield 1
의 yield 1
에서 1을 반환하고 정지한다. 그렇기 때문에 value의 값은 1이다.
두번째 gen.next().value
가 실행되면 let first =
부분부터 실행된다. 하지만 이전 단계에서 값을 이미 반환했기 때문에 first에는 어떠한 값도 할당 되지 않는다. 그래서 first의 값은 undefined고
let second = yield first + 2
에서 yield first + 2
까지 실행되지만 first가 undefined이기 때문에 first + 2는 NaN이다.
next() 메소드 호출 시 인자 값을 전달한 경우이다.
gen.next().value // 1
gen.next(10).value // 12
gen.next(20).value // 23
gen.next(10)
가 실행되면 first에 10이 할당되고 yield first + 2
에 의해 12가 반환된다.
const ajaxCalls = () => {
const res1 = fetch.call('https://api.github.com/users?since=1000')
const res2 = fetch.call(`https://api.github.com/users/${res1[3]}`)
}
const m = ajaxCalls()
위 코드는 동기적으로 처리 되는 코드이다.
server에 request를 보내고, server에서 response가 오기까지 시간이 걸린다.
때문에 res1에는 response 데이터가 아닌 불필요한 데이터가 담긴다.
원하는 데이터를 담기 위해서는 비동기처리가 필요하다.
$.ajax({
method: 'GET',
url: 'https://api.github.com/users?since=1000',
success: function(res){
const res2 = fetch.call(`https://api.github.com/users/${res[3]}`)
}
})
fetch.call('https://api.github.com/users?since=1000')
.then( res => {
const res2 = fetch.call(`https://api.github.com/users/${res[3]}`)
})
const fetchWrapper = (gen, url) => fetch(url)
.then(res => res.json())
.then(res => gen.next(res)); // 결과 값이 req1에 담김
function* getNthUserInfo() {
const [gen, from, nth] = yield; // 첫 단계
const req1 = yield fetchWrapper(gen, `https://api.github.com/users?since=${from || 0}`);
const userId = req1[nth -1 || 0].id;
console.log(userId);
const req2 = yield fetchWrapper(gen, `https://api.github.com/user/${userId}`);
console.log(req2);
}
const runGenerator = (generator, ...rest) => {
const gen = generator();
gen.next(); // 첫 단계 수행, yield에서 정지
gen.next([gen, ...rest]); // [gen, from, nth] = [gen, ...rest]
}
runGenerator(getNthUserInfo, 1000, 4)
// 1004
// response 내용...
gen.next()
가 실행되면 const [gen, from, nth] = yield;
의 yield
에서 정지
다음 gen.next([gen, ...rest])
가 실행되면 [gen, from, nth] = [gen, ...rest]...
yield fetchWrapper(gen, ~)
에서 정지
fetchWrapper에서 서버에서 요청을 보내고 res를 받아 그 값을 인자 값으로 next() 메소드를 호출 -> req1에는 서버의 res 값이 담기고 userId 값을 얻어내어 다음 yield
에서 멈춤
다시 한번 fetchWrapper 함수가 실행되고 userId에 해당하는 res값이 req2에 담김. 끝.
async / await
편안...
const fetchJson = async (from, nth) => {
const req1 = await fetch(`https://api.github.com/users?since=${from || 0}`)
.then(res => res.json())
const userId = req1[nth -1 || 0].id;
const req2 = await fetch(`https://api.github.com/user/${userId}`)
const data = await req2.json()
console.log(data)
}
fetchJson(1000, 4)