yield pattern

2 분 소요

yield 패턴에 대해서 알아보겠습니다.

정의

yield 키워드는 제너레이터 함수 (function* 또는 레거시 generator 함수)를 중지하거나 재개하는데 사용됩니다.
함수 실행 중 yield 를 만나면 결과를 리턴하고 함수는 종료되지 않고 대기 합니다.

문법 및 동작

yield를 사용하려면 함수뒤에 를 붙여야 합니다. 즉, function 로 표기하여야 합니다.
일반적으로 yield 함수를 generator에 넣고, next()를 호출하여 함수를 수행합니다.
이 때, 함수는 yield 를 만날 때까지 수행되며, yield를 만나면 결과를 리턴합니다.
이 때, next()를 다시 호출하면, 함수는 대기한 그 다음 구문부터 실행한다. 시작은 대기 이후부터 실행하나 그 이전에 선언된 변수나 처리된 값도 모두 적용됩니다.

function* add() {
  let a = 1;
  let b = 2;
  yield a + 2;
  yield b + 2;
}

var generator = add(1);
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

// {value: 3, done: false}
// {value: undefined, done: true}

yield로 나온 결과는 value와 done으로 json형태로 표현됩니다.
done는 함수가 모두 종료되거나 return을 만나면 true로 표기됩니다.

promise와 비교

javascript는 비동기이기 때문에 함수 실행 도중 다른 함수의 실행 결과를 대기할 수 없습니다.
이를 극복하기 위해 promise와 async await을 사용하였습니다.

let result = await add(3,5);
console.log(result);

function add(a,b) {
  return new Promise((resolve, reject) => {
    resolve(a+b);
  });
}

promise를 사용하면 함수가 정리가 잘 되지만 함수가 복잡해져 읽기가 쉽지 않습니다.
그러나 이를 yield 를 활용하면 조금 더 간결해질 수 있습니다.

let generator = add(3,5);
console.log(generator.next());
console.log(generator.next());

function* add(a,b) {
  yield a+b;
}

// {value: 3, done: false}
// {value: undefined, done: true}

단점이라면 선언부와 실행부가 나뉘므로 문장을 읽기는 쉬우나 이해하기는 쉽지 않을 수 있습니다.

callback hell, promise hell의 개선

콜백헬은 이제 유명한 문제입니다.

function cadd2(val, callback) {
	return callback(val + 2);
}

console.log(cadd2(1, function (val) {
	return val + 4;
}));

이에 대한 대안으로 promise를 활용하고 있습니다.
그러나 promise의 결과를 또 다른 promise에 반영하다보면 then 안에 또 다른 promise가 반복되면서 promise hell이 되는 또 다른 문제가 발생합니다.

function add2(val) {
	return new Promise((resolve, reject) => {
		resolve(val + 2);
	});
}

function add4(val) {
	return new Promise((resolve, reject) => {
		resolve(val + 4);
	});
}

add2(1).then(result => {
	add4(result).then(final => {
		console.log(final); 
	});
});

이를 yield를 활용하여 매우 깔끔하게 정리할 수 있습니다.

function* add2(val) {
	yield val + 2;
}
function* add4(val) {
	yield val + 4;
}

var generator = add2(1);
var result = generator.next();
var generator2 = add4(result.value);
console.log(generator2.next());

yield와 return

yield 함수에서 함수를 모두 마치지 않고 중간에 return을 만나면 어떻게 될까요?
결론을 말하자면 return을 만나는 순간 done이 true가 되며, 그 이후의 next는 허용되지 않으며, 종료된 함수로 인식합니다.

let generator = add(3,5);
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

function* add(a,b) {
  yield a+b+0;
  return a+b+1;
  yield a+b+2;
}

// {value: 8, done: false}
// {value: 9, done: true}
// {value: undefined, done: true}

yield*

만일 yield에 또 다른 yield 함수를 실행하려면 yield*를 활용하여야 하며, 그렇지 않으면 에러가 발생합니다.

function* add(val) {
	yield* add4(val + 2);
}
function* add4(val) {
	yield val + 4;
}

var generator = add(1);
console.log(generator.next());
console.log(generator.next());

// {value: 7, done: false}
// {value: undefined, done: true}

예제를 실행해보면 yield -> yield 이므로 next() -> next() 를 해야할 것 같지만, yield* 이므로 next() 한번에 실제 yield를 만날 때까지 수행되어 한번에 실행됨을 확인할 수 있습니다.

결론

yield는 비동기에서 promise 외의 또 다른 동기 실행 방식이며, 함수를 중간에 대기시킬 수 있다는 점은 promise보다 더 나은 기능이라고 볼 수 있습니다.
그러나 남발하면 눈에는 쉬우나 이해하기 어려운 코드가 될 수 있으므로 적절한 조절이 필요할 것 같습니다.

참고사이트

댓글남기기