내부 요소들을 공개적으로 탐색(반복)할 수 있는 데이터 구조.
[Symbol.iterator] 메소드를 가지고 있다.
const arr = ['a', 'b', 'c']
const set = new Set(['a', 'b', 'c'])
const map = new Map([[false, 'no'], [true, 'yes'], ['well', 'soso']])
const str = '문자열도 이터러블하다!?!!'
console.dir([1, 2, 3])
console.dir(new Set([1, 2, 3]))
console.dir(new Map([[0, 1], [1, 2], [2, 3]]))
- 없는 경우
const obj = { 0: 1, 1: 2, 2: 3, length: 3 }
console.dir(obj)
function* generator () {
yield 1
yield 2
yield 3
}
const gene = generator()
console.dir(gene)
const arr = [1, 2, 3]
const map = new Map([['a', 1], ['b', 2], ['c', 3]])
const set = new Set([1, 2, 3])
const str = '이런것도 된다.'
const gene = (function* () {
yield 1
yield 2
yield 3
})()
const arrFromArr1 = Array.from(arr)
const arrFromMap1 = Array.from(map)
const arrFromSet1 = Array.from(set)
const arrFromStr1 = Array.from(str)
const arrFromGene1 = Array.from(gene)
const arrFromArr2 = [...arr]
const arrFromMap2 = [...map]
const arrFromSet2 = [...set]
const arrFromStr2 = [...str]
const arrFromGene2 = [...gene]
const [arrA, , arrC] = arr
const [mapA, , mapC] = map
const [ , setB, setC] = set
const [ , strB, ...strRest] = str
const [geneA, ...geneRest] = gene
console.log(arrA, arrC)
console.log(mapA, mapC)
console.log(setB, setC)
console.log(strB, strRest)
console.log(geneA, geneRest)
for (const x of arr) {
console.log(x)
}
for (const x of map) {
console.log(x)
}
for (const x of set) {
console.log(x)
}
for (const x of str) {
console.log(x)
}
for (const x of gene) {
console.log(x)
}
const a = [
new Promise((resolve, reject) => { setTimeout(resolve, 500, 1) }),
new Promise((resolve, reject) => { setTimeout(resolve, 100, 2) }),
3456,
'abc',
new Promise((resolve, reject) => { setTimeout(resolve, 300, 3) }),
]
Promise.all(a)
.then(v => { console.log(v) })
.catch(err => { console.error(err) })
const s = new Set([
new Promise((resolve, reject) => { setTimeout(resolve, 300, 1) }),
new Promise((resolve, reject) => { setTimeout(resolve, 100, 2) }),
new Promise((resolve, reject) => { setTimeout(reject, 200, 3) }),
])
Promise.race(s)
.then(v => { console.log(v) })
.catch(err => { console.error(err) })
const arr = [1, 2, 3]
const map = new Map([['a', 1], ['b', 2], ['c', 3]])
const set = new Set([1, 2, 3])
const str = '이런것도 된다.'
const makeGenerator = iterable => function* () {
yield* iterable
}
const arrGen = makeGenerator(arr)()
const mapGen = makeGenerator(map)()
const setGen = makeGenerator(set)()
const strGen = makeGenerator(str)()
console.log(arrGen.next())
console.log(mapGen.next())
console.log(...setGen)
console.log(...strGen)
여기까지 모두 내부적으로는
Symbol.iterator
또는generator
을 실행하여 iterator로 변환한 상태에서next()
를 반복 호출하는 동일한 로직을 기반으로 함.
const obj = {
a: 1,
b: 2,
[Symbol.iterator] () {
return 1
}
}
console.log([...obj])
new Map()
new Set()
new WeakMap()
new WeakSet()
Promise.all()
Promise.race()
Array.from()
반복을 위해 설계된 특별한 인터페이스를 가진 객체.
- 객체 내부에는
next()
메소드가 있는데, - 이 메소드는
value
와done
프로퍼티를 지닌 객체를 반환한다. done
프로퍼티는 boolean값이다.
초간단 이터레이터 예시
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())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
const iter = {
next () {}
}
const iter = {
next () {
return {}
}
}
const iter = {
next () {
return {
done: false
}
}
}
console.log(iter.next())
const iter = {
val: 0,
next () {
const isDone = ++this.val >= 5
return {
done: isDone,
value: !isDone ? this.val : undefined
}
}
}
console.log(iter.next())
- 기본 이터레이터에 접근하기
const arr = [ 1, 2 ]
const arrIterator = arr[Symbol.iterator]()
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
- 객체가 이터러블한지 확인하기
const isIterable = target => typeof target[Symbol.iterator] === 'function'
for...of
,...(spread)
등은 모두 개체 내부 (또는 개체의__proto__
)의 [Symbol.iterator]를 실행한 결과를 바탕으로,done
이true
가 될 때까지 계속하여next()
를 호출하는 식으로 구현되어 있다.
// 절대 실행하지 말 것!!
const createIterator = () => {
return {
next () {
return {
done: false
}
}
}
}
const obj = {
[Symbol.iterator]: createIterator
}
console.log(...obj)
// 절대 실행하지 말 것!!
const obj = {
[Symbol.iterator]() {
return this
},
next() {
return {
done: false
}
}
}
console.log(...obj)
const createIterator = () => {
let count = 0
return {
next () {
return {
done: count > 3
}
}
}
}
const obj = {
[Symbol.iterator]: createIterator
}
console.log(...obj)
const createIterator = function () {
let count = 0
const items = Object.entries(this)
return {
next () {
return {
done: count >= items.length,
value: items[count++]
}
}
}
}
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
[Symbol.iterator]: createIterator
}
console.log(...obj)
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
[Symbol.iterator] () {
let count = 0
const items = Object.entries(this)
return {
next () {
return {
done: count >= items.length,
value: items[count++]
}
}
}
}
}
console.log(...obj)
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
*[Symbol.iterator] () {
yield* Object.entries(this)
}
}
console.log(...obj)
for-of
,...(spread operator)
,forEach 메소드
등은 내부적으로[Symbol.iterator]
를 실행한 결과 객체를 들고,- 객체 내부의
next()
메소드를 done 프로퍼티
가true
가 나올 때까지 반복하여 호출한다.- 즉, Symbol.iterator 메소드의 내용을 위 요구사항에 맞추어 구현하기만 하면 iterable한 객체이다.
Duck type Protocol : 덕타이핑
- 중간에서 멈췄다가 이어서 실행할 수 있는 함수.
- function 키워드 뒤에
*
를 붙여 표현하며, 함수 내부에는yield
키워드를 활용한다. - 함수 실행 결과에 대해
next()
메소드를 호출할 때마다 순차적으로 제너레이터 함수 내부의yield
키워드를 만나기 전까지 실행하고,yield
키워드에서 일시정지한다. - 다시
next()
메소드를 호출하면 그 다음yield
키워드를 만날 때까지 함수 내부의 내용을 진행하는 식이다.
function* gene () {
console.log(1)
yield 1
console.log(2)
yield 2
console.log(3)
}
const gen = gene()
- 선언 방식
function* gene () { yield }
const gene = function* () { yield }
const obj = {
gene1: function* () { yield }
*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)
const obj = {
a: 1,
b: 2,
c: 3,
*[Symbol.iterator] () {
for (let prop in this) {
yield [prop, this[prop]]
}
}
}
console.log(...obj)
for (let p of obj) {
console.log(p)
}
- yield* 은 다시 뒤에 이터러블 객체를 받아서, 하나씩 (next() 호출시) 꺼냄
function* gene () {
yield* [1, 2, 3, 4, 5]
yield
yield* 'abcde'
}
for (const c of gene()) {
console.log(c)
}
// 1,2,3,4,5,undefined, a,b,c,d,e
- generator의 done 값이 false가 나올때 까지 yield한 효과를 냄
function* gene1 () {
yield [1, 10]
yield [2, 20]
}
function* gene2 () {
yield [3, 30]
yield [4, 40]
}
function* gene3 () {
console.log('yield gene1')
yield* gene1() // 이 yield* 하나가 아래 실행과 동일
// yield [1, 10]
// yield [2, 20]
console.log('yield gene2')
yield* gene2()
// yield [3, 30]
// yield [4, 40]
console.log('yield* [[5, 50], [6, 60]]')
yield* [[5, 50], [6, 60]]
// yield [5, 50]
// yield [6, 60]
console.log('yield [7, 70]')
yield [7, 70]
}
const gen = gene3()
for (let [k, v] of gen) {
console.log(k, v)
}
function* gene () {
let first = yield 1 // yield가 밖에 나갔다오면 yield1은 사라지고 undefined가 first에 할당됨
let second = yield first + 2 // 때문에 first + 2는 NaN 이 됨
yield second + 3
}
const gen = gene()
console.log(gen.next().value) //1
console.log(gen.next().value) //3
console.log(gen.next().value) //6
function* gene () {
let first = yield 1 // 나갔다 들어올 때 first yield1 는 외부에서 넘겨준 10으로 치환됨
let second = yield first + 2 //12를 밖으로 내보내고 들어올때 first + 2는 외부에서 받은 20으로 치환됨
yield second + 3 //외부에 23을 내보냄
}
const gen = gene()
console.log(gen.next(10).value) //1
console.log(gen.next(20).value) //3
console.log(gen.next().value) //6
- 이전의 비동기 작업 수행
// 깃헙에서 유저정보를 가져오는 api
// 하려고 하는 것은 다음과 같음
// 우선 userId가 1000번 이후인 데이터를 가져와서
// 그 중에 4번째에 위치한 User 정보를 보고싶다.
// 1003이라고 썻지만 사실 1001, 1002, 1003이 모두 데이터를 가지고 있지 않을 수 있다. 1002번이 탈퇴해서 빌수도
// 진짜 하고싶은 것은 /user/${rest1[3]} 처럼 4번째 유저를 가져오는 것임
const ajaxCalls = () => {
const res1 = fetch.get('https://api.github.com/users?since=1000')
const res2 = fetch.get('https://api.github.com/user/${rest1[3]}')
}
const m = ajaxCalls()
// fetch 메소드는 html5 메소드로 get으로 url을 넣어 call 하면
// sever에 request 보내고,
// sever에서 response가 옴
// 그러나 ,request 와 response 사이의 시간은 서버의 상태와 네트워크 속도에 따라 천차만별
// 그래서 일단 fetch.get() 요청을 하자마자, rest1에 결과값을 담아버림,
// 즉 response된 결과가 담기는게 아니라, 불필요한 데이터가 담김
// 그래서 rest2 의 줄에서 /user/${rest1[3]} 를 조회해도 무의미함
// 이것이 비동기 처리의 고민이다.
// 그래서 옛날에 어떻게 했냐하면?
//콜백방식의 Jquery 비동기 처림
$.ajax({
method: 'GET',
url: 'https://api.github.com',
success: function(res1){
const res2 = fetch.get('https://api.github.com/user/${res1[3]}')
}
})
// 이런식으로 success가 완료되면 콜백 function을 실행하는 식으로 처리함
// 그러다가 다음으로 Promise 객체 등장
// then 키워드가 sucess를 의미하게 됨, fetch.get()의 결과가 Promise 객체를 반환함
fetch.get('https://api.github.com/users?since=1000').then(function(res1){
const res2 = fetch.get('https://api.github.com/user/${rest1[3]}')
})
- Generator 방식의 비동기 처리
const fetchWrapper = (gen, url) => fetch(url) // 서버에 get 요청을 보냄
.then(res => res.json())
.then(res => gen.next(res));
// 비동기 처리인데 동기적으로 처리하는 것 처럼 조작할 수 있음
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();
gen.next([gen, ...rest]);
}
runGenerator(getNthUserInfo, 1000, 4);
runGenerator(getNthUserInfo, 1000, 6);
const fetchWrapper = url => fetch(url).then(res => res.json());
function* getNthUserInfo() {
const [from, nth] = yield;
const req1 = yield fetchWrapper(`https://api.github.com/users?since=${from || 0}`);
const userId = req1[nth - 1 || 0].id;
console.log(userId);
const req2 = yield fetchWrapper(`https://api.github.com/user/${userId}`);
console.log(req2);
}
const runGenerator = (generator, ...rest) => {
const gen = generator();
gen.next();
gen.next([...rest]).value
.then(res => gen.next(res).value)
.then(res => gen.next(res));
}
runGenerator(getNthUserInfo, 1000, 4);
runGenerator(getNthUserInfo, 1000, 6);