async 이터레이터와 제너레이터

async 이터레이터와 제너레이터

비동기 이터레이터, 제너레이터 사용하여 비동기적으로 들어오는 데이터를 필요에 따라 처리할 수 있다.

async 이터레이터

일반 이터러블

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let range = {
from: 1,
to: 5,

// for..of 최초 실행 시, Symbol.iterator가 호출됩니다.
[Symbol.iterator]() {
// Symbol.iterator메서드는 이터레이터 객체를 반환합니다.
// 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데,
// 다음 값은 next()에서 정해집니다.
return {
current: this.from,
last: this.to,

// for..of 반복문에 의해 각 이터레이션마다 next()가 호출됩니다.
next() {
// (2)
// next()는 객체 형태의 값, {done:.., value :...}를 반환합니다.
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
},
};
},
};

for (let value of range) {
alert(value); // 1, 2, 3, 4, 5
}

이터러블 객체를 비동기적으로 만들기 위한 작업

  1. Symbol.iterator 대신, Symbol.asyncIterator를 사용해야 한다.
  2. next() 는 프라미스를 반환해야 한다.
  3. 비동기 이터러블 객체를 대상으로 하는 반복 작업은 for await (let item of iterable) 반복문을 사용해 처리해야 한다.

1초마다 비동기적으로 값을 반환하는 이터러블 객체를 만들어 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let range = {
from: 1,
to: 5,

// for await..of 최초 실행 시, Symbol.asyncIterator가 호출됩니다.
[Symbol.asyncIterator]() {
// (1)
// Symbol.asyncIterator 메서드는 이터레이터 객체를 반환합니다.
// 이후 for await..of는 반환된 이터레이터 객체만을 대상으로 동작하는데,
// 다음 값은 next()에서 정해집니다.
return {
current: this.from,
last: this.to,

// for await..of 반복문에 의해 각 이터레이션마다 next()가 호출됩니다.
async next() {
// (2)
// next()는 객체 형태의 값, {done:.., value :...}를 반환합니다.
// (객체는 async에 의해 자동으로 프라미스로 감싸집니다.)

// 비동기로 무언가를 하기 위해 await를 사용할 수 있습니다.
await new Promise((resolve) => setTimeout(resolve, 1000)); // (3)

if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
},
};
},
};

(async () => {
for await (let value of range) {
// (4)
alert(value); // 1,2,3,4,5
}
})();

일반 이터레이터와, async 이터레이터에 차이점

  1. 객체를 비동기적으로 반복 가능하도록 하려면, Symbol.asyncIterator메서드가 반드시 구현되어야 함.
  2. Symbol.asyncIterator는 프라미스를 반환하는 next()가 구현된 객체를 반환해야 한다.
  3. next()aync메서드일 필요는 없지만 await를 쓰면 편하기 때문에 편의상 이렇게 사용한다.
  4. 반복 작업을 하려면 for await를 붙인다. 그럼 range[Symbol.asyncIterator] ()가 일회 호출되는데, 그 이후엔 각 값을 대상으로 next()가 호출된다.

전개 문법 ...은 동기적으로 동작하지 않는다. 동기 작업이 필요한 이터레이터는 Symbol.asyncIterator가 아닌 Symbol.iterator을 찾기 때문이다.

async 제너레이터

일반 제너레이터

1
2
3
4
5
6
7
8
9
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}

for (let value of generateSequence(1, 5)) {
alert(value); // 1, then 2, then 3, then 4, then 5
}

async를 붙여주면 비동기적으로 제너레이터를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
// await를 사용할 수 있습니다!
await new Promise((resolve) => setTimeout(resolve, 1000));

yield i;
}
}

(async () => {
let generator = generateSequence(1, 5);
for await (let value of generator) {
alert(value); // 1, 2, 3, 4, 5
}
})();

aync 제너레이의 generator.next()의 메서드는 비동기적이 되고 프라미스를 반환한다. 따라서 await 키워드를 사용해서 generator.next()를 사용하도록 한다.

1
result = await generator.next(); // result = {value: ..., done: true/false}

async 이터러블

반복 가능한 객체를 만들려면 객체에 Symbol.iterator를 추가해야 한다.

1
2
3
4
5
6
7
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return <range를 반복가능하게 만드는 next가 구현된 객체>
}
}

일반적으로 제너레이터를 반환하도록 구현하는 경우가 더 많다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let range = {
from: 1,
to: 5,

*[Symbol.iterator]() {
// [Symbol.iterator]: function*()를 짧게 줄임
for (let value = this.from; value <= this.to; value++) {
yield value;
}
},
};

for (let value of range) {
alert(value); // 1, 2, 3, 4, 5
}

지금 이 상태에서 비동기 동작을 축하려면, Symbol.iteratorasync Symbol.asyncIterator로 바꿔야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let range = {
from: 1,
to: 5,

async *[Symbol.asyncIterator]() {
// [Symbol.asyncIterator]: async function*()와 동일
for (let value = this.from; value <= this.to; value++) {
// 값 사이 사이에 약간의 공백을 줌
await new Promise((resolve) => setTimeout(resolve, 1000));

yield value;
}
},
};

(async () => {
for await (let value of range) {
alert(value); // 1, 2, 3, 4, 5
}
})();

실제 사례