제네레이터, 또는 async함수와 같은 코루틴은 로직을 일시중단(yield또는 await을 통해)할 수 있습니다. 기존의 콜백패턴에선 pause된 상태를 구분 하기 위한 플래그 변수가 필요한데 코루틴 패턴에선 (일시중단할 수 있는)제어흐름으로써 대신 표현가능합니다.
BEFORE:
const el = document.querySelector('.draggable')
let isTouching = false // <= 플래그변수, 로직이 복잡할 수록 늘어남
el.addEventListener('touchstart', () => {
isTouching = true
})
el.addEventListener('mousemove', event => {
if (isTouching) {
handle(ev.offsetX, ev.offsetY)
}
})
el.addEventListener('touchend', () => {
isTouching = false
})
// ☝️ 콜백이 touchstart, mousemove, touchend 로직의 순서대로 작성되서
// 보기 편하지만 로직이 복잡하면 콜백패턴은 순차성을 잃고 금방 뒤죽박죽 작성할 수 밖에 없게 됩니다.
AFTER:
const el = document.querySelector('.draggable')
while (true) {
await once(el, 'touchstart')
for await (const event of on(el, 'mousemove', { until: once(el, 'touchend') })) {
handle(event.offsetX, event.offsetY)
}
}
isTouching 플래그변수가 코드 블럭(await on(el, 'touchstart')이후 부터 while 블럭 까지
)으로 대체됩니다.
콜백패턴에서 플래그 변수는 로직이 복잡해질수록 쉽게 늘어납니다. 변수가 늘어날수록 변수가 조합될 수 있는 경우의 수는 폭발적으로 증가합니다.
콜백의 호출순서가 외부 푸시에 의존하기 때문에 플래그의 전이(transition)과정이 코드상에 표현되지 않습니다.
반면 코루틴에서는 제어흐름으로 표현된 플래그는 경우의 수를 줄여주고 플래그의 전이(transition)과정 역시 코드로 전달 할 수 있습니다.
---------끝
밑에는 AFTER 헬퍼함수, 비슷한 lib 많아영. 급하게 짠거라서 오류있을수 있음
const once = (el, name) =>
new Promise(resolve =>
el.addEventListener('name', function fn(event) {
el.removeEventListener('name', fn)
resolve(event)
})
)
const on = (el, name, { until }) => ({
[Symbol.iterator]: () => {
const buf = []
let done = false
let resolve
const fn = event => {
if (resolve) {
resolve({ value: event })
resolve = null
} else {
buf.push(event)
}
}
el.addEventListener(name, fn)
until?.then(end)
function end() {
done = true
el.removeEventListener(name, fn)
if (resolve) resolve({ done })
}
return {
async next() {
if (done) return { done }
if (buf.length) return { value: buf.shift() }
return new Promise(r => (resolve = r))
},
async return() {
end()
return { done }
},
}
},
})
👍