2 분 소요

배경

부트캠프의 이번 과제인 로그라이크 제작에 있어서 게임을 만들 때 시간 제한이 있으면 좋겠다고 생각했다.
다양한 시간 제한을 걸 수 있겠지만 가능하면 내가 커맨드를 입력하기 전에 시간이 지나 버리면 입력 값을 캔슬하고 별도의 값이 반환되도록 하는, 인터럽트 상황을 만들어보고 싶어졌다.
마침 비동기 관련한 것을 배우기도 했으니 이번 기회에 비동기적인 코드를 작성하고자 가벼운 마음으로 시작하게 되었지만, 생각보다 난이도가 있었다.

발단

방법 자체를 모르다 보니 여러 조언을 들어 보았는데, 관건은 resolve할 값을 고르면 된다는 것이었다.
처음 시도한 것은 하나의 promise 안에 조건을 걸고 입력을 바꾸는 것이었다.

async function GetinputWithTimeout(question, timeout, defaultValue) {
    return new Promise(resolve => {
        const timer = setTimeout(() => {
            console.log(`시간이 초과되었습니다! ${defaultValue}를 반환합니다.`);
            resolve(defaultValue);
        }, timeout);

        const userinput = readlineSync.question(question);

        clearTimeout(timer);

        resolve(defaultValue || userinput);//결과적으로 실패했다.
    }
    )

}

이렇게 작성하면 결국 밑의 question에 잡히게 되어 이벤트가 돌지 않았다.
시간이 아무리 지나도 이 방법으론 내가 입력한 값을 반환하는 건 덤이었다.

전개

비동기 함수의 경우 콜스택에 쌓이기 전에 이벤트 스택에 먼저 쌓인다는 걸 보고 난 이후 해결책이 한 동안 안 보였는데, 그러다 Promise.race라는 기능을 알게 되었다.
여러 promise를 배열로 받아서 그 가운데 가장 빨리 처리되는 값을 반환하는 것을 보고 있자니 이건가 하는 생각으로 바로 적용해 보았다.

async function GetinputWithTimeout_withRace(question, timeout, defaultValue) {
    return Promise.race(
        [new Promise((resolve) => {
            setTimeout(() => {
                console.log("타임 아웃!");
                resolve(defaultValue);
            }, timeout);
        }),

        new Promise((resolve)=>
        {
            let userinput = readlineSync.question(question);
            resolve(userinput);
        })
        ]
    )

}

위기

race를 사용하면 여러 개의 값 가운데 하나를 보내는 것이라 다른 값을 보내는 것은 가능했지만, readlinesync를 정지시키는 것은 할 수 없었다.
좀 더 조사해 보니 race 내에 들어 있는 비동기 함수들은 어찌되었건 실행은 되고 있는 상태라는 것이었다.
정말 답이 없는 건가 고민하고 있을 때, 그냥 readline을 안 쓰고 다른 입력 방법을 써보자고 생각하게 되었다.

절정

기본 함수가 결국 이런 함수들의 베이스가 되는 함수이다 보니, 기존에 제공하는 process.stdin을 사용하기로 했다.
이게 최선이 아닌 건 알겠지만 일단 적용해 보기로 했다.

export async function getinputwithtimeout_without_ReadlineSync(question, timeout, defaultValue) {
    let inputReceived = false;

    return new Promise((resolve) => {
        const timer = setTimeout(() => {
            if (!inputReceived) {
                resolve(defaultValue);
                process.stdin.pause();//입력 대기 종료
            }
        }, timeout);

        process.stdin.resume();
        process.stdout.write(question);

        process.stdin.once('data', (data) => {
            if (!inputReceived) {
                inputReceived = true;
                clearTimeout(timer);
                const userinput = data.toString().trim();
                resolve(userinput);
                process.stdin.pause();
            }
        })
    })


}

결말

어떻게든 해결은 되었지만 함수들 하나하나를 해석하라고 하면 그건 또 힘들 것 같다.
일단 비동기 처리나 이벤트 처리 같은 것이 아직 많이 어렵다는 점과, 그걸 감안 하더라도 여러 가지로 이득이 많다는 정도를 느꼈다.
위의 코드도 한 차례 수정을 거친 것이었는데 아마도 이후에 process.stdin.resume();의 위치를 바꾸고 once를 수정하는 등의 공정을 더 거쳐야 할 것 같다.