Knock!Knock! 코루틴 #2
이전 글: Stackful/Stackless 코루틴
간단하지만 동작하는 예제 코드 덕분인지 첫 댓글이 달렸습니다!! 이 글은 원래 글의 곁다리입니다.
이벤트 핸들러를 코루틴으로 만들어본 위 예제에 Jeong-Ik Cha님이 “시간"을 고려해보면 어떻겠냐고 댓글을 달아주셨네요.
이전 글의 데모에서는 노크코드의 길이만큼 입력 이벤트를 받아서 성공/실패를 판단하였습니다. 그런데 노크코드의 길이가 드러나는 것은 문제가 있습니다. 아무래도 보안상 취약하겠죠? 편의성을 조금 저해하더라도 입력 pause가 일정 시간이 넘을 때 성공/실패를 판단하기로 해 보죠.
저라면 Rx로 어떻게든 엮어보겠지만 이 글은 “코루틴을 쓴다면 어떤 모양이 될까” 알아보는 게 취지인 만큼 기존 코루틴 코드를 요구사항에 맞게 바꿔보기로 했습니다.
처음엔 딱히 방법이 떠오르지 않아서 우선 변경된 로직이 어떤 모양으로 구현되어야 할지 생각하면서 위처럼 작성해 봤습니다. 처음 입력을 받으면 그 다음부터는 1초 동안의 타임아웃을 가지고서 입력을 기다립니다. 타임아웃되기 전에 입력이 있다면 계속 다음 입력을 기다립니다.
입력에 대한 타임아웃을 어떻게 할까 하다가 처음엔 redux-saga의 영향으로 다음처럼 select를 고려했는데, 너무 오버인것 같더군요.
yield select('click', timeout(1000))
변경 내용을 보면 yield
를 통해 이벤트 핸들러로 전달되는 이벤트를 가져오는 것은 변함 없습니다. 하지만 이번에는 yield
인자로 타이머를 전달하는 기능이 추가되었습니다. 마치 Java의 동시성 패키지들이 wait 함수들에서 타임아웃 시간을 지정할 수 있는 것과 같지요.
타이머는 그냥 단순 객체(eg, {type: ‘timeout', duration: 1000}
)로 만들어서 코루틴 드라이버가 이를 해석하도록 할까 하다가 Promise 객체로 해 봤습니다. 이렇게 해서 yield
timeout는 마치 Async 코루틴처럼 동작할 수 있는 거죠. yield
timeout에서 Promise가 먼저 resolve되면 그 값이 기본값으로 반환됩니다.
원래의 coro()
함수는 단순히 이벤트가 발생하면 코루틴을 resume하기만 했습니다. 이제 coro()
가 할 일이 많아졌습니다. 타임아웃을 처리해야죠.
wireTimeout()
함수가 코루틴에서 전달할 지 모를 Promise객체를 이용하여 타임아웃을 설정합니다. 이때 타임아웃보다 이벤트가 먼저 발생하는 경우를 고려해야 합니다. 해당 타임아웃을 무효화해야 합니다. 조금 복잡해 보이긴 하지만 나름 괜찮게 합쳐진것 같네요.
완성된 코드와 데모는 아래에서 보실 수 있습니다.