진취적 삶
46 제너레이터와 async/await 본문
46.1 제너레이터란?
코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할수있는 특수한 함수다 .
- 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도할수있다. 일반 함수를 호출하면 제어권이 함수에게 넘어가고 함수 코드를 일괄 실행한다. 즉 함수 호출자는 함수를 호출한 이후 함수 실행을 제어할수없다. 제너레이터 함수는 함수 실행을 함수 호출자가 제어할수 있다. 함수의 제어권을 함수가 독점하는 것이 아니라 함수 호출자에게도 양도할수 있다.
- 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을수 있다. 일반 함수를 호출하면 매개변수를 통해 함수 외부에서 값을 주입받고 함수 코드를 일괄 실행하여 결과값을 함수 외부로 반환한다 . 제너레이터 함수는 함수 호출자와 양방향으로 함수의 상태를 주고받을수 있다. 다시말해 제너레이터 함수는 함수 호출자에게 상태를 전달할수 있고 함수 호출자로부터 상태를 전달받을수 있다.
- 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다 . 일반 함수를 호출하면 함수 코드를 일괄 실행하고 값을 반환한다. 제너레이터 함수를 호출하면 함수 코드를 실행하는 것이 아니라 이터러블이면서 동시에 이터레이터인 제너레이터 객체를 반환한다.
46.2 제너레이터 함수의 정의
제너레이터 함수는 function* 키워드로 선언한다. 하나 이상의 yield 표현식을 포함한다.
- 은 function 키워드 바로 뒤에 붙이는 것을 권장
//제너레이터 함수 선언문
function* getDecFunc() {
yield 1;
}
//제너레이터 함수 표현식
const getExpFunc = function () {
yield 1 ;
}
//제너레이터 메서드
const obj = {
*getObjMethod() {
yield 1 ;
}
}
//제너레이터 클래스 메서드
class myClass {
*genClsMethod() {
yield 1
}
}
제너레이터 함수는 화살표 함수와 new 연산자와 함께 생성자 함수로 호출할수 없다 .
46.3 제너레이터 객체
제너레이터 함수를 호출하면 일반 함수처럼 함수 코드 블록을 실행하는것이 아니라 제너레이터 객체를 생성해 반환한다. 제너레이터 함수가 반환환 제너레이터 객체는 이터러블이면서 동시에 이터레이터다 .
제너레이터 객체는 Symbol.iterator 메서드를 상속받는 이터러블이면서 value,done프로퍼티를 갖는 이터레이터 result 객체를 반환하는 next 메서드를 소유하는 이터레이터다 .
- next 메서드를 호출하면 제너레이터 함수의 yield 표현식 까지 코드 블록을 실행하고 yield 된 값을 value 프로퍼티 값으로 ,false 를 done 프로퍼티 값으로 갖는 이터레이터 result 객체를 반환한다.
- return 메서드를 호출하면 인수로 전달받은 값을 value 프로퍼티 값으로 ,true를 done 프로퍼티 값으로 갖는 이터레이터 result 객체를 반환한다.
function* getFunc() {
yield 1;
yield 2;
yield 3;
}
//제너레이터 함수를 호출하면 제너레이터 객체를 반환한다.
const generator = getFunc();
//제너레이터 객체는 이터러블이면서 동시에 이터레이터다 .
console.log(Symbol.iterator in generator); //true
console.log("next" in generator); //true
- next 메서드를 호출하면 제너레이터 함수의 yield 표현식 까지 코드 블록을 실행하고 yield된 값을 value 프로퍼티 값으로, false를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환
- return 메서드를 호출하면 인수로 전달받은 값을 value 프로퍼티 값으로 true를 done 프로퍼티 값으로 갖는 이터레이터 result 객체를 반환
function* genFunc() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.error(e);
}
}
const generator = genFunc();
console.log(generator.next()); //{ value: 1, done: false }
console.log(generator.next()); //{ value: 2, done: false }
console.log(generator.return("end")); //{ value: 'end', done: true }
46.4 제너레이터의 일시 중지와 재개
yield 키워드와 next 메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할수 있다. 제너레이터는 함수 호출자에게 제어권을 양도하여 필요한 시점에 함수 실행을 재개할수있다.
제너레이터 함수를 호출하면 제너레이터 함수의 코드 블록이실행하는것이 아니라 제너레이터 객체를 반환한다고 했다.
이터러블이면서 동시에 이터레이터인 제너레이터 객체는 next 메서드를 갖는다.
yield 키워드는 제너레이터 함수의 실행을 일시 중지시키거나 yield 키워드 뒤에 오늘 표현식의 평가 결과를 제너레이터 함수 호출자에게 반환한다.
//제너레이터 함수
function* genFunc() {
yield 1;
yield 2;
yield 3;
}
//제너레이터 함수를 호출하면 제너레이터 객체를 반환
//이터러블이면서 동시에 이터레이터인 generator 객체는 next 메서드를 갖는다.
const generator = genFunc();
console.log(generator.next()); //{ value: 1, done: false }
console.log(generator.next()); //{ value: 2, done: false }
console.log(generator.next()); //{ value: 3, done: false }
console.log(generator.next()); //{ value: undefined, done: true }
제너레이터 객체의 next 메서드를 호출하면 yield 표현식까지 실행되고 일시 중지된다. 이때 함수의 제어권이 호출자로 양도된다.
generator 객체의 next 메서드는 value, done 프로퍼티를 갖는 이터레이터 result 객체를 반환한다. result 객체의 value 프로퍼티에는 yield 표현식에서 yield 된 값이 할당되고 done 프로퍼티에는 제너레이터 함수가 끝까지 실행되었는지를 나타내는 불리언값이 할당된다.
function* genFunc() {
// 처음 next 메서드 호출하면 첫번째 yield 표현식 까지 실행되고 일시 중지한다.
//이때 yield 된 값 1은 next 메서드가 반환한 이터레이터 result 객체의 value 프로퍼티에 할당한다
//x 변수에는 아직 아무것도 할당되지 않는다. x 변수의 값은 next 메서드가 두번째호출될때
const x = yield 1;
//두번째 next 메서드 호출하면 x에 10 할당 두번째 yield 표현식까지 실행되고 일시 중지
// x+10은 next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에 할당
const y = yield x + 10;
//세번쨰 next 메서드 호출하면 const y = 20
//세번째 next 메서드를 호출하면 함수 끝까지 실행된다.
//함수의 반환값 x+y는 next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에 할당된다.
return x + y;
}
const generator = genFunc();
let res = generator.next(0);
console.log(res); //{ value: 1, done: false }
res = generator.next(10);
console.log(res); //{ value: 20, done: false }
res = generator.next(30);
console.log(res); //{ value: 40, done: true }
46.5 제너레이터의 활용
46.5.1 이터러블의 구현
const infiniteFibonacci = (function* () {
let [pre, cur] = [0, 1];
while (true) {
[pre, cur] = [cur, pre + cur];
yield cur;
}
})();
for (const num of infiniteFibonacci) {
if (num > 10000) break;
console.log(num);
}
46.5.2 비동기 처리
const fetch = require("node-fetch");
//제너레이터 실행기
const async = (generatorFunc) => {
//generatorFunc 호출후 generator 에 할당
const generator = generatorFunc(); //2
//프로미스가 resolved 상태가 되면 호출하는 함수
const onResolved = (arg) => {
//generator.next 를 통해서 다음에 실행 yield 문가져온다.
//yield 문에 넘길 값을 result 에 저장
const result = generator.next(arg); //5
//result.done 이 true이면 result.value false 면 result.then 호출
//onResolve(res) 함수 반환
return result.done ? result.value : result.then((res) => onResolved(res)); //7
};
return onResolved; //3
};
//제너레이터 함수 매개변수
async(function* fetchTodo() {
//1
//fetch 메소드를 이용해 API로부터 데이터를 받는다.
const url = "<https://jsonplaceholder.typicode.com/todos/1>";
const response = yield fetch(url); //6
const todo = yield response.json(); //8
console.log(todo);
})();
이 코드는 자바스크립트의 제너레이터(generator)와 프로미스(promise)를 이용해서 비동기 코드를 동기적으로 실행하는 기능을 구현한 코드입니다.
우선 async 함수가 선언되어 있습니다. 이 함수는 제너레이터 함수를 매개변수로 받습니다.
async 함수에서는 받은 제너레이터 함수를 실행하기 위해 **generatorFunc()**을 호출하고, 그 결과를 generator 변수에 할당합니다.
그리고 onResolved 함수를 정의합니다. 이 함수는 프로미스가 resolved 상태가 되면 호출되는 콜백 함수입니다.
onResolved 함수는 **generator.next()**를 호출해서 다음에 실행할 yield 문을 가져옵니다. 이 때, yield 문에 넘길 값은 result 변수에 저장됩니다.
**result.done**이 **true**이면 **result.value**를 반환합니다. 그렇지 않으면 result.then() 메소드를 호출합니다. 이 때, result.then() 메소드의 콜백 함수에는 onResolved() 함수를 전달합니다.
async 함수는 onResolved 함수를 반환합니다.
마지막으로 async 함수를 호출합니다. 여기서는 **fetchTodo**라는 제너레이터 함수를 매개변수로 전달합니다. fetchTodo 함수 안에서는 fetch() 메소드를 이용해서 API로부터 데이터를 가져옵니다.
yield 키워드를 이용해서 프로미스 객체를 반환합니다. 이렇게 하면 fetch() 메소드가 반환하는 프로미스가 resolve될 때까지 다음 코드가 실행되지 않습니다.
그 다음에는 **response.json()**을 호출합니다. 이 메소드도 프로미스를 반환하므로, yield 키워드를 이용해서 프로미스 객체를 반환합니다.
마지막으로, todo 변수에 할당된 객체를 콘솔에 출력합니다.
이렇게 함으로써, 자바스크립트의 제너레이터와 프로미스를 이용해서 비동기 코드를 동기적으로 실행할 수 있게 됩니다.
제너레이터 실행기가 필요하다면 직접 구현보다는 co 라이브러리를 사용하자
46.6 async/await
async/await 는 프로미스를 기반으로 동작한다. 프로미스의 than/catch/finally 후속처리 메서드에 콜백 함수를 던잘해서 비동기 처리 결과를 후속 처리할 필요 없이 마치 동기 처리처럼 프로미스를 사용할수 있다.
import fetch from 'node-fetch';
//제너레이터 실행기
const async = (generatorFunc) => {
const generator = generatorFunc();
const onResolved = (arg) => {
const result = generator.next(arg);
return result.done ? result.value : result.then((res) => onResolved(res));
};
return onResolved;
};
async(async function* fetchTodo() {
const url = "<https://jsonplaceholder.typicode.com/todos/1>";
const response = yield fetch(url);
const todo = yield response.json();
console.log(todo);
})();
- async 함수를 호출할 때, 제너레이터 함수 **fetchTodo**를 매개변수로 전달합니다.
- async 함수에서 **generatorFunc**를 실행하여 제너레이터 객체 **generator**를 생성합니다.
- onResolved 함수를 반환합니다. 이 함수는 뒤에서 사용됩니다.
- fetchTodo 제너레이터 함수가 실행됩니다. yield 키워드를 사용하여 비동기 작업을 순서대로 처리합니다.
- const response = yield fetch(url); 구문에서 fetch 함수를 호출하고, 서버로부터 응답을 받을 때까지 제너레이터가 일시 중지됩니다. 응답이 도착하면 제너레이터가 다시 실행됩니다.
- const todo = yield response.json(); 구문에서 response 객체의 json() 메서드를 호출하여 JSON 형식의 데이터를 파싱합니다. 마찬가지로 제너레이터가 일시 중지됩니다. 파싱이 완료되면 제너레이터가 다시 실행됩니다.
- console.log(todo); 구문에서 파싱된 데이터를 출력합니다.
- async 함수의 반환값인 onResolved 함수는, **arg**를 매개변수로 받아서, **generator.next(arg)**를 호출합니다. 이 때, **generator.next()**는 제너레이터 함수의 실행을 계속할 수 있도록 하는 역할을 합니다. 만약 제너레이터 함수가 끝나지 않았다면, **result.then((res) => onResolved(res))**를 반환하여 다음 비동기 작업이 완료될 때까지 기다립니다.
- async 함수를 호출하면서 fetchTodo 제너레이터 함수를 전달합니다. 이제 비동기 작업이 순차적으로 처리되며, 마지막으로 파싱된 데이터가 출력됩니다.
46.6.1 async 함수
await 키워드는 반드시 async 함수 내부에서 사용해야한다. async 함수는 async 키워드를 사용해 정의하며 언제나 프로미스를 반환한다.
async 함수가 명시적으로 프로미스를 반환하지 않더라고 async 함수는 암묵적으로 반환값을 resolve 하는 프로미스를 반환한다.
//async 함수 선언문
async function foo(n) {
return n;
}
foo(1).then((v) => console.log(v));
//async 함수 표현식
const bar = async function (n) {
return n;
};
bar(2).then((v) => console.log(v));
//async 화살표 함수
const baz = async (n) => n;
baz(3).then((v) => console.log(v));
//async 메서드
const obj = {
async foo(n) {
return n;
},
};
obj.foo(4).then((v) => console.log(v));
//async 클래스 메서드
class MyClass {
async bar(n) {
return n;
}
}
const myClass = new MyClass();
myClass.bar(5).then((v) => console.log(v));
클래스의 construtor 메서드든 인스턴스를 반환해야하지만 async 함수는 언제나 프로미스를 반환해야 한다 .
46.6.2 await 키워드
프로미스가 settled 상태 ( 비동기 처리가 수행된 상태) 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve 한 처리 결과를 반환한다. await 키워드는 반드시 프로미스 앞에서 사용해야 한다.
const fetch = require((node = fetch));
const getGithubUserName = async (id) => {
const res = await fetch(`https://api.github.com/users/${id}`);
const { name } = await res.json();
console.log(name);
};
getGithubUserName("suha");
await 키워드는 settled 상태가 될 때가지 대기한다.
fetch 함수가 수행한 HTTP 요청에 대한 서버의 응답이 도착해서 fetch 함수가 반환한 프로미스가 settled 상태가 될때까지 대기한다.
이후 프로미스가 settled 상태가 되면 프로미스가 resolve한 처리 결과가 res 변수에 할당된다.
async function foo() {
const a = await new Promise((resolve) => setTimeout(() => resolve(1), 3000));
const b = await new Promise((resolve) => setTimeout(() => resolve(2), 2000));
const c = await new Promise((resolve) => setTimeout(() => resolve(3), 1000));
console.log(a, b, c); //6초 걸림
}
foo();
foo 함수가 수행하는 3대의 비동기 처리는 서로 연관이 없이 개별적으로 수행하는 비동기 처리이기때문에 all로 병렬적으로 처리
async function foo() {
const res = await Promise.all([
new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
new Promise((resolve) => setTimeout(() => resolve(3), 1000)),
]);
console.log(res); //3초걸림
}
foo();
아래의 코드는 앞선 비동기 처리 결과를 가지고 다음 비동기 처리를 수행해야 한다.
비동치 처리의 순서가 보장되어야 하므로 모든 프로미스에 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]); //6초
}
bar(1);
46.4.3 에러 처리
async/ await 에서는 try/ catch 문을 사용할수 있다.
async 함수 내에서 catch 문을 사용해서 에러 처리를 하지 않으면 async 함수는 발생한 에러를 reject 하는 프로미스를 반환한다.
const fetch = require(node - fetch);
const foo = async () => {
try {
const wrongUrl = "<https://wrong.url>";
const response = await fetch(wrongUrl);
const data = await response.json();
console.log(data);
} catch (err) {
console.error(err);
}
};
foo();
'개발 도서 > 자바스크립트 deepdive' 카테고리의 다른 글
47 에러처리 (0) | 2023.07.13 |
---|---|
48 모듈 (0) | 2023.07.13 |
36 디스트럭처링 할당 (0) | 2023.07.13 |
37 set 과 map (0) | 2023.07.13 |
38 브라우저의 렌더링 과정 (0) | 2023.07.13 |