[JavaScript] 복수 개의 비동기 함수 관리와 예외처리

 


 이번 포스팅에서는 복수 개의 비동기 함수를 실행하고, 그 중 발생하는 오류에 대해서 예외처리하는 방법을 다룹니다.

이 글은 2019년도 진행된 FEConf Korea의 유인동님 강의 내용을 기반으로 하고 있습니다. (링크)

(주제에 대한 내용 뿐만 아니라 Promise, Generator 등 예외처리에 대한 지향점도 포괄하고 있으니 영상도 보시면 좋습니다.)



1. 예제 내용 설명

 1) n개의 비동기 작업을 수행할 자료가 있음.

 2) 순차적으로 수행되어야 하며, 오류가 발생한 경우 이 후 순번의 비동기 처리는 수행되지 않아야 함.



2. 비동기 함수 예제 (Timeout) - 사용자 정의 함수

 1) 비동기로 수행되는 'setTimeout' 함수를 통해 2초 동안 작업을 수행한다고 가정하는 함수 정의.

 2) 2초 동작 수행 후, 전달받은 자료의 SEQ값과 만기일(EXPRIE_DT)에 대한 문자열을 반환.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const UserAsyncFunc = function(task){

    return new Promise((resolve, reject)=>{

        let expireDt = new Intl.DateTimeFormat("ko").format(new Date(task.EXPIRE_DT));

        /*  Aync Function ... */
        setTimeout(()=> {
            resolve(`Finish Work ... ${task.SEQ} : ${expireDt}`);
        }, 2000);

    });
}



3. 비동기 함수를 처리할 실행자 함수 정의

[# 1안]

단순 반복을 통해 비동기 사용자함수 (UserAsyncFunc)를 수행. await을 통해 지연 처리.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/**
 * n개 작업을 수행
 * 
 * @param {*} jobs 
 */
const ExcuteAsyncJobs = async function(jobs) {

    /* 1안 */
    for(const task of jobs){
        await UserAsyncFunc(task)
            .then(msg => console.log(msg))
    }

}


[# 2안]

작업목록 단위(jobs)로 반복했던 '1안'과 달리, 작업수행 단위(Worker)로 반복. (Generator 함수)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * n개 작업을 수행
 * 
 * @param {*} jobs 
 */
const ExcuteAsyncJobs = async function(jobs) {

    /* 2안 */
    function* Worker(executor, iter){
        /* 데이터 셋 개수 만큼 반복 */ 
        for(const i of iter){
            // yield i instanceof Promise ? i.then(executor) : executor(i);
            yield executor(i);
        }
    }

    for await (const result of Worker(UserAsyncFunc, jobs)){
        console.log(result);
    }

}


1,2안 모두 같은 결과를 만들어 내는 반복문이지만, 예제와 같이 단순한 코드가 아닐 경우엔 2안이 가시성이 좋아보입니다. 



4. 실행 결과

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const jobs = [
    { SEQ: '001', PRODUCT: 'Apple', PRICE: '1000', EXPIRE_DT: '2024-11-13'},
    { SEQ: '002', PRODUCT: 'Banan', PRICE: '2000', EXPIRE_DT: '2024-07-09'},
    { SEQ: '003', PRODUCT: 'Tomato', PRICE: '2500', EXPIRE_DT: '2023-06-08'},
    { SEQ: '004', PRODUCT: 'Melon', PRICE: '4500', EXPIRE_DT: '2024-09-11'}
]

ExcuteAsyncJobs(jobs)
    .then(msg => console.log('success'))
    .catch(error => console.log(error));




5. 예외 처리

 2번 째 행의 자료에 대한 만기일(EXPIRE_DT)를 날짜형이 아닌 값('apple')로 지정하여, 

앞서 정의한 사용자 함수 (UserAsyncFunc)의 날짜형 변환(new Intl.DateTimeFormat)의 오류 발생을 유도합니다.
실행자 함수(ExcuteAsyncJobs)의 catch절을 제외한 내용입니다.

[예제]

1
2
3
4
5
6
7
8
9
const jobs_err = [
    { SEQ: '001', PRODUCT: 'Apple', PRICE: '1000', EXPIRE_DT: '2024-11-13'},
    { SEQ: '002', PRODUCT: 'Banan', PRICE: '2000', EXPIRE_DT: 'apple'},         // Error Case
    { SEQ: '003', PRODUCT: 'Tomato', PRICE: '2500', EXPIRE_DT: '2023-06-08'},
    { SEQ: '004', PRODUCT: 'Melon', PRICE: '4500', EXPIRE_DT: '2024-09-11'}
]

ExcuteAsyncJobs(jobs_err)
    .then(msg => console.log('success'));

[결과]





6. 결론

소개글의 영상은 보신 분은 아시겠지만, 주안점은 사용자 함수(UserAsyncFunc)의 예외처리를 실행자 함수(ExcuteAsyncJobs)에서 하지 않고 실행자를 호출하는 곳에서 처리한다는 것입니다.

ExcuteAsyncJobs 함수에서 'try..catch' 문을 통해 예외처리 할 수 있으나, 사용처에서 명확한 오류를 확인하기 위해서는 오류를 방치하는 것에 의의를 둔다고 할 수 있겠습니다.


7. 전체 코드

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
export default async () => {

    const jobs = [
        { SEQ: '001', PRODUCT: 'Apple', PRICE: '1000', EXPIRE_DT: '2024-11-13'},
        { SEQ: '002', PRODUCT: 'Banan', PRICE: '2000', EXPIRE_DT: '2024-07-09'},
        { SEQ: '003', PRODUCT: 'Tomato', PRICE: '2500', EXPIRE_DT: '2023-06-08'},
        { SEQ: '004', PRODUCT: 'Melon', PRICE: '4500', EXPIRE_DT: '2024-09-11'}
    ]

    const jobs_err = [
        { SEQ: '001', PRODUCT: 'Apple', PRICE: '1000', EXPIRE_DT: '2024-11-13'},
        { SEQ: '002', PRODUCT: 'Banan', PRICE: '2000', EXPIRE_DT: 'apple'},         // Error Case
        { SEQ: '003', PRODUCT: 'Tomato', PRICE: '2500', EXPIRE_DT: '2023-06-08'},
        { SEQ: '004', PRODUCT: 'Melon', PRICE: '4500', EXPIRE_DT: '2024-09-11'}
    ]

    ExcuteAsyncJobs(jobs_err)
        .then(msg => console.log('success'))
        .catch(error => console.log(error));
}


const UserAsyncFunc = function(task){

    return new Promise((resolve, reject)=>{

        let expireDt = new Intl.DateTimeFormat("ko").format(new Date(task.EXPIRE_DT));

        /*  Aync Function ... */
        setTimeout(()=> {
            resolve(`Finish Work ... ${task.SEQ} : ${expireDt}`);
        }, 2000);

    });
}


/**
 * n개 작업을 수행
 * 
 * @param {*} jobs 
 */
const ExcuteAsyncJobs = async function(jobs) {

    /* 1안 */
    // for(const task of jobs){
    //     await UserAsyncFunc(task)
    //         .then(msg => console.log(msg))
    // }


    /* 2안 */
    function* Worker(executor, iter){
        /* 데이터 셋 개수 만큼 반복 */ 
        for(const i of iter){
            // yield i instanceof Promise ? i.then(executor) : executor(i);
            yield executor(i);
        }
    }

    for await (const result of Worker(UserAsyncFunc, jobs)){
        console.log(result);
    }

}


0 댓글