Daylogs/Etc

구글 앱스 스크립트에서 비동기 작업 추가하기

ohgyun 2021. 3. 10. 01:34

발생일: 2021.03.10

키워드: 구글 앱스 스크립트, 구글 앱 스크립트, Google Apps Script, gas, background, setTimeout

문제:

 

구글 스프레드시트와 슬랙, 구글 앱스 스크립트로 간단한 자동화 태스크를 구축해뒀다.

슬랙을 API 창구로, 구글 앱스 스크립트를 앱 서버로, 스프레드시트를 디비로 사용하고 있다.

 

예를 들면 이런 작업이다.

1. 슬랙에서 /find_stock_price 같은 슬래시 명령을 치면

2. 구글 앱스 스크립트에서 요청을 받아서

3. 스프레드시트의 값을 수정하거나 조회한 후에 슬랙으로 응답해주는 것이다.

 

태스크가 조금 복잡해지면서 작업의 응답 시간이 길어지게 됐다.

문제는 슬랙 커맨드의 타임아웃이 3초라는 것...

 

슬랙 요청에 대해 짧은 응답을 먼저 주고,

긴 작업을 수행한 후에 파라미터로 받은 response_url 로 다시 메시지를 주면 간단한 일이지만,

안타깝게도 구글 앱스 스크립트는 모든 스크립트가 동기로 동작한다.

 

setTimeout()을 지원하지 않는다.

즉, 일반적인 자바스크립트처럼 비동기로 백그라운드 작업을 실행하고 응답을 먼저 줄 수가 없다.

 

V8 런타임이 추가되면서 Promise 나 async 함수가 추가됐지만,

내부적인 동작일 뿐 함수 자체는 동기로 동작한다.

 

어떻게 하면 될까?

 


해결책:

 

ClockTrigger 를 사용하는 방법으로 우회했다.

요청이 들어오면 바로 직후에 실행되는 타임 트리거를 생성하고 먼저 응답한다.

슬랙 메시지에 응답하기 위한 URL은 스프레드시트에 저장해두는 방법.

 

... 장황하지만 동작은 잘 된다.

아래는 코드 스니핏.

 

 

function doPost(e) {

    const command = e.parameter.command;

    const responseUrl = e.parameter.response_url;

    let outputText = '';

 

    if (command === '/long_task') {

        runAsyncTask('longTask', responseUrl);

        outputText = '작업 중...';

    } ... 중략

 

    const output = {
        text: outputText
    };
    return ContentService.createTextOutput(JSON.stringify(output))

        .setMimeType(ContentService.MimeType.JSON);

}

 

function runAsyncTask(funcName, responseUrl) {
    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = spreadsheet.getSheetByName('sample');

    // responseUrl 을 스프레드시트에 기록해둔다
    sheet.getRange('A1').setValue(responseUrl);

    ScriptApp.newTrigger(funcName)
        .timeBased()
        .after(10)
        .create();
}

 

function longTask() {

    // 오래 걸리는 작업

    doSomethingLongTask();

 

    // responseUrl 을 가져온다.

    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = spreadsheet.getSheetByName('sample');

    const responseUrl = sheet.getRange('A1').getValue();

 

    // 슬랙으로 다시 전달

    UrlFetchApp.fetch(responseUrl, {

        method: 'post',

        contentType: 'application/json',

        payload: JSON.stringify({ text: '작업 완료' })

    });

}

 

 


참고:
- Slack Message Interaction: api.slack.com/legacy/interactive-messages

- ClockTrigger API: developers.google.com/apps-script/reference/script/clock-trigger-builder#after(Integer)

반응형