제너레이터란?
- 코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수
- 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도할 수 있다
- 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을 수 있다
- 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다
제너레이터 함수의 정의
- function* 키워드로 선언
- 하나 이상의 yield 표현식 포함
- 에스터리스크의 (*) 위치는 function 키워드와 함수 이름 사이라면 어디든지 상관 X 하지만 일관성을 위해 function 키워드 바로 뒤를 권장
- 제너레이터 함수는 화살표 함수로 정의 X
- new 연산자와 함께 생성자 함수로 호출 X
// 제너레이터 함수 선언문
function* getDecFunc(){
yield 1;
};
// 제너레이터 함수 표현식
const getExpFunc = function* (){
yield 1;
};
// 제너레이터 메서드
const obj = {
* getObjMethod(){
yield 1;
}
};
// 제너레이터 클래스 메서드
class MyClass{
*genClsMethod(){
yield 1;
}
}
제너레이터 객체
- 제너레이터 함수를 호출하면 일반 함수처럼 함수 코드 블록을 실행하는 것이 아니라 제너레이터 객체를 생성해 반환
- 제너레이터 함수가 반환한 제너레이터 객체는 이터러블이면서 동시에 이터레이터
- Symbol.iterator 메서드를 상속받는 이터러블이면서 value , done 프로퍼티를 갖는 이터레이터 리절트 객체를 반화하는 next 메서드를 소유하는 이러테이터 (Symbol.iterator 메서드 호출하여 이터레이터 생성 필요 X)
// 제너레이터 함수
function* getFunc(){
yield 1;
yield 2;
yield 3;
}
// 제너레이터 함수를 호출하면 제너레이터 객체를 반환
const generator = getFunc();
// 제너레이터 객체는 이터러블이면서 동시에 이터레이터
// 이터러블은 Symbol.iterator 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 객체
console.log(Symbol.iterator in generator); // true
// 이터레이터는 next 메서드를 갖는다
console.log('next' in generator); // true
- 제너레이터 객체는 next 메서드를 갖는 이터레이터이지만 이터레이터에는 없는 return, throw 메서드를 가짐
- next 메서드 호출 : 제너레이터 함수의 yield 표현식까지 코드 블록을 실행하고 yield 된 값을 value 프로퍼티 값으로 false를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환
- return 메서드 호출 : 인수로 전달받은 값을 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환
- throw 메서드 호출 : 인수로 전달받은 에러를 발생시키고 undefined 를 value 프로퍼티 값으로, true 를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체 반환
function* getFunc(){
try{
yield 1;
yield 2;
yield 3;
} catch(e){
console.error(e);
}
}
const generator = getFunc();
console.log(generator.next()); // { value : 1, done : false }
console.log(generator.return('End!')); // { value : 'End!', done : true }
console.log(generator.throw('Error')); // { value : undefined, done : true }
제너레이터의 일시 중지와 재개
- yield 키워드와 next 메서드를 통해 실행을 일시 중지했다가 필요한 시점에 재개 가능
- 제터레이터 함수를 호출하면 함수의 코드 블록이 실행되는 것이 아니라 제너레이터 객체를 반환하므로 제너레이터 객체의 next 메서드를 호출하면 제너레이터 함수의 코드 블록 실행
- yield 키워드는 제너레이터 함수의 실행을 일시 중지시키거나 yield 키워드 뒤에 오는 표현식의 평가 결과를 제너레이터 함수 호출자에게 반환
// 제너레이터 함수
function* getFunc(){
yield 1;
yield 2;
yield 3;
}
// 제너레이터 함수를 호출하면 제너레이터 객체 반환
// 이터러블이면서 동시에 이터레이터인 제너레이터 객체는 next 메서드를 가짐
const generator = getFunc();
// 처음 next 메서드를 호출하면 첫 번째 yield 표현식까지 실행되고 일시 중지
// next 메서드는 이터레이터 리절트 객체 ({ value, done }) 를 반환
// value 프로퍼티에는 첫 번째 yield 표현식에서 yield된 값 1이 할당
// done 프로퍼티에는 제너레이터 함수가 끝까지 실행되었는지를 나타내는 false가 할당
console.log(generator.next()); // {value : 1, done : false}
// 다시 next 메서드를 호출하면 두 번째 yield 표현식까지 실행되고 일시 중지
// next 메서드는 이터레이터 리절트 객체 ({ value, done }) 를 반환
// value 프로퍼티에는 첫 번째 yield 표현식에서 yield된 값 2 할당
// done 프로퍼티에는 제너레이터 함수가 끝까지 실행되었는지를 나타내는 false가 할당
console.log(generator.next()); // {value : 2, done : false}
// 다시 next 메서드를 호출하면 세 번째 yield 표현식까지 실행되고 일시 중지
// next 메서드는 이터레이터 리절트 객체 ({ value, done }) 를 반환
// value 프로퍼티에는 첫 번째 yield 표현식에서 yield된 값 3 할당
// done 프로퍼티에는 제너레이터 함수가 끝까지 실행되었는지를 나타내는 false가 할당
console.log(generator.next()); // {value : 3, done : false}
// 다시 next 메서드를 호출하면 남아있는 yield 표현식이 없으므로 제너레이터 함수의 마지막까지 실행됨
// next 메서드는 이터레이터 리절트 객체 ({ value, done }) 를 반환
// value 프로퍼티에는 함수의 반환값 undefined 가 할당
// done 프로퍼티에는 제너레이터 함수가 끝까지 실행되었음을 나타내는 true 가 할당
console.log(generator.next()); // {value : undefined, done : true}
- 제너레이터 객체의 next 메서드를 호출하면 yield 표현식까지 실행되고 일시 중지 (이 때 함수의 제어권이 호출자로 양도됨)
- 이때 제너레이터 객체의 next 메서드는 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환
- next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에는 yield 표현식에서 yield 된 값 (yield 키워드 뒤의 값)이 할당되고 done 프로퍼티에는 제너레이터 함수가 끝까지 실행되었는지를 나타내는 불리언 값이 할당
제너레이터의 활용
1. 이터러블의 구현
// 무한 이터러블을 생성하는 제너레이터 함수
const infiniteFibo = (funciton* (){
let [pre, cur] = [0, 1];
while(true){
[pre, cur] = [cur, pre + cur];
yield fur;
}
})());
for(const num of infiniteFibo){
if(num > 1000) break;
console.log(num); // 1 2 3 5 8 13 ... 6765
}
2. 비동기 처리
// 제너레이터 함수를 인자로 받아 비동기적으로 실행하는 함수
const async = generatorFunc => {
// 제너레이터 함수를 실행하여 제너레이터 객체를 생성
const generator = generatorFunc();
// 재귀적으로 호출되는 함수로, 제너레이터의 각 단계를 진행
const onResolved = arg => {
// 제너레이터의 next 메서드를 호출하여 다음 단계로 진행
// 여기서 arg는 이전 yield의 결과값으로 사용됩니다.
const result = generator.next(arg);
// 만약 제너레이터 함수가 완료되었다면(result.done === true),
// 최종 결과값을 반환
if (result.done) {
return result.value;
} else {
// 제너레이터 함수가 아직 완료되지 않았다면(result.done === false),
// yield된 프로미스를 기다린 후, 완료되면 onResolved를 다시 호출
// 이는 비동기 작업이 완료될 때까지 반복됩니다.
return result.value.then(res => onResolved(res));
}
};
// onResolved 함수를 호출하여 제너레이터 함수의 실행 시작
return onResolved;
};
// 위에서 정의한 async 함수를 사용하여 제너레이터 함수를 비동기적으로 실행
(async(
function* fetchTodo(){
// 데이터를 받을 URL을 정의
const url = '데이터를 받을 url';
// 첫 번째 yield에서 fetch 요청을 보내고, 그 결과를 기다림
const response = yield fetch(url);
// 두 번째 yield에서 fetch 요청의 결과를 JSON으로 변환하고, 그 결과를 기다림
const data = yield response.json();
// JSON으로 변환된 데이터를 콘솔에 출력
console.log(data);
}
))();
async/ await
- 프로미스의 후속 처리 메서드 없이 마치 동기처럼 프로미스가 처리 결과를 반환하도록 구현 가능
1. async 함수
- await 함수는 반드시 async 함수 내부에서 사용해야함
- async 함수는 async 키워드를 사용해 정의하며 언제나 프로미스 반환
- async 함수가 명시적으로 프로미스를 반환하지 않더라도 암묵적으로 반환값을 resolve하는 프로미스 반환
// async 함수 선언문
async function foo(n){ return n }
foo(1).then(v => console.log(v)); // 1
// async 함수 표현식
const bar = async function(n){ return n }
bar(2).then(v => console.log(v)); // 2
// async 화살표 함수
const baz = async n => n;
baz(3).then(v => console.log(v)); // 3
// async 메서드
const obj = {
async foo(n){ return n };
};
obj.foo(4).then(v => console.log(v)); // 4
// async 클래스 메서드
class myClass{
async bar(n){ return n; }
}
const myClass = new MyClass();
myClass.bar(5).then(v => console.log(v)); // 5
2. await 함수
- 프로미스가 settled 상태 (비동기 처리가 수행된 상태)가 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve 한 처리 결과 반환
- 비동기 처리 순서가 보장되어야할 때 사용 (비동기 처리가 서로 연관 없이 개별적으로 수행되는 처리이면 await 불필요)
async function bar(n) {
const a = await new Promise((resolve) => setTimeout(() => resolve(n), 3000));
// 두 번째 비동기 처리를 수행하려면 첫 번째 비동기 처리 결과 필요
const b = await new Promise((resolve) => setTimeout(() => resolve(a + 1), 2000));
// 번째 비동기 처리를 수행하려면 두 번째 비동기 처리 결과 필요
const c = await new Promise((resolve) => setTimeout(() => resolve(b + 1), 1000));
console.log([a, b, c]); // [1, 2, 3]
}
bar(1); // 약 6초 소요
3. 에러 처리
- async/await 에서 에러 처리는 try ... catch 문 사용 가능
- 프로미스를 반환하는 비동기 함수는 명시적으로 호출할 수 있기 때문
- async 함수 내에서 catch 문을 사용해서 에러 처리를 하지 않으면 async 함수는 발생한 에러를 reject 하는 프로미스 반환 (Promise.prototype.catch 후속 처리 메서드 사용 가능)
const foo = async () => {
try {
const response = await fetch('잘못된 url');
const data = await response.json();
console.log(data);
} catch (error) {
console.error(err); // TypeError: Failed to fetch
}
};
foo();
/*
foo()
.then(console.log)
.catch(console.log); // TypeError: Failed to fetch
*/
'Javascript' 카테고리의 다른 글
| [모던 자바스크립트 Deep Dive] 44. 모듈 (0) | 2023.12.17 |
|---|---|
| [모던 자바스크립트 Deep Dive] 43. 에러 처리 (1) | 2023.12.17 |
| [모던 자바스크립트 Deep Dive] 41. 프로미스 (1) | 2023.12.16 |
| [모던 자바스크립트 Deep Dive] 40. REST API (0) | 2023.12.11 |
| [모던 자바스크립트 Deep Dive] 39. Ajax, JSON, XMLHttpRequest (0) | 2023.12.11 |