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.iterator를 async 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
}
})();

실제 사례