Skip to content

Instantly share code, notes, and snippets.

@skt-t1-byungi
Last active September 2, 2022 11:33
Show Gist options
  • Save skt-t1-byungi/62859a4de077f51e1e8a6e5d555adc8d to your computer and use it in GitHub Desktop.
Save skt-t1-byungi/62859a4de077f51e1e8a6e5d555adc8d to your computer and use it in GitHub Desktop.
코루틴설명

제네레이터, 또는 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 }
            },
        }
    },
})
@skt-t1-byungi
Copy link
Author

skt-t1-byungi commented Sep 2, 2022

rxjs 버젼

import { firstValueFrom, fromEvent, takeUntil } from 'rxjs';
import { eachValueFrom } from 'rxjs-for-await';

const el = document.querySelector('.draggable');

while (true) {
  await firstValueFrom(fromEvent(el, 'touchstart'));

  for await (const event of eachValueFrom(
    fromEvent(el, 'touchmove').pipe(takeUntil(fromEvent(el, 'touchend'))),
  )) {
    handle(event.offsetX, event.offsetY);
  }
}

or

import { exhaustMap, fromEvent, takeUntil } from 'rxjs';
import { eachValueFrom } from 'rxjs-for-await';

const el = document.querySelector('.draggable');

const events$ = fromEvent(el, 'touchstart').pipe(
  exhaustMap(() =>
    fromEvent(el, 'touchmove').pipe(takeUntil(fromEvent(el, 'touchend'))),
  ),
);

for await (const event of eachValueFrom(events$)) {
  handle(event.offsetX, event.offsetY);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment