Node.js는 Javascript로 개발한다. 아래 코드를 주목해보자.


'use strict';

try {
    null.error;
} catch (e) {
    console.error('catch error: ', e.stack);
}


평상시 에러 잡는 코드를 작성할 때 사용한다. 아무런 의구심도 들지 않는다.
자연스럽게 catch에서 실행될 것이란 걸 알기 때문이다.


아이러니하게도, 비동기 방식으로 개발하게 된다면 상황은 달라진다.
순차적으로 진행되는 코드에 익숙한 게 대부분이다. 들쑥날쑥 동작하는 코드를 보는 순간 당황할 수 있다.


'use strict';

const fs = require('fs');

try {
    fs.readFile('bad', (err) => {
        throw new Error('test error');
    });
} catch (e) {
    console.error('catch error: ', e.stack);
}


비동기 방식으로 파일을 읽고 있으며, 동일하게 에러를 발생시킨다.
catch로 들어오지 못하고, process exit 되는 상황에 놓이게 된다.
callback function은 try ~ catch scope에서 벗어나기 때문에 잡을 수 없다.


'use strict';

const fs = require('fs');

try {
    fs.readFile('bad', (err) => {
        try {
            throw new Error('test error');
        } catch (e) {
            console.error('fs catch error: ', e.stack);
        }
    });
} catch (e) {
    console.error('catch error: ', e.stack);
}


사용하던 대로, 벗어나고 있는 scope에 새로운 try ~ catch를 사용하면 생각대로 진행된다.


전적으로 의존한다면, try ~ catch 천국이 될 것이다.


좀 더 나은 방법으로 고민해보면, node.js의 domain module을 사용하면 된다.
현재도 Deprecated로 명시가 되어있으나, 마땅한 대안이 없어 유지되고 있는 것으로 보인다.


쉽게 보면, global scope, local scope 2가지가 전부다.


'use strict';

const fs = require('fs');
const domain = require('domain');

const fsProtect = domain.create();

fsProtect.on('error', (err) => {
    console.log(err);
    console.error('domain error: ', err.stack);
});

fsProtect.run(() => {
    fs.readFile('bad', (err) => {
        throw new Error('test error');
    });
});


run 함수는, try ~ catch scope와 동일하게 보면 이해가 쉽게 된다.
callback function의 영역도 scope에 포함 되기 때문에, error 이벤트로 진입하게 된다.


{ Error: test error
    at ReadFileContext.fs.readFile [as callback] (error.js:14:15)
    at FSReqWrap.readFileAfterOpen [as oncomplete] (fs.js:365:13)
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  domainThrown: true }


error 객체를 자세히 살펴보면, 다른 점이 한가지 있다. run 함수 scope 안에서 에러가 발생하는 경우,
domain 객체가 property로 제공되며, _events에 error 이벤트가 등록되어 있다. 이 점 때문에 error 이벤트를 사전에 등록해줘야만 한다.


간편하게 사용할 수 있는 대표적인 방법이며, 대부분 위와 같은 방식을 사용한다.
한가지 의구심이 드는 부분은, 원하는대로 scope 조절은 불가능 하다는 것이다.


'use strict';

const fs = require('fs');
const domain = require('domain');

const fsProtect = domain.create();

fsProtect.on('error', (err) => {
    console.log(err);
    console.error('domain error: ', err.stack);
});

fs.readFile('bad', fsProtect.intercept((err) => {
    throw new Error('test error');
}));


try ~ catch scope를 생각해보자. 벗어나는 부분만, intercept 함수로 scope 영역을 확대했으며,
동일하게 error 이벤트가 등록되어 있다. scope 조절은 입맛에 맞게 할 수 있다.


일반적으로 프로젝트에 투입되게 되면, web server app, scheduler app을 개발하게 된다.


'use strict';

const http = require('http');
const server = http.createServer();

const domain = require('domain');

server.on('request', (req, res) => {
    const httpProtect = domain.create();

    httpProtect.add(req);
    httpProtect.add(res);

    httpProtect.on('error', (err) => {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'text/plain');
        res.end(`${err.message}\n`);
    });

    httpProtect.run(() => {
        // throw new Error('test error');

        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Hello World\n');
    });
});

server.listen(3000, '0.0.0.0', () => {
    console.log('server listening');
});


위 코드는 global scope 형태를사용하고 있는데, add 함수는 scope 영역에 객체를 포함한다는 의미이며, 에러가 발생하는 경우, 등록된 error 이벤트로 진입하게 된다.


추가로 고민해야 할 부분이 있는데, 주석을 풀고 요청을 해보자. process exit 될 것이다. error 이벤트로 진입 시 추가적인 에러가 발생했기 때문이다. 보통 error 처리 함수로 정의하고 구현하는데, scope에서 벗어난 영역이므로, 다시 scope를 정의하려고 하는 생각이 일반적이다.


'use strict';

const http = require('http');
const server = http.createServer();

const domain = require('domain');

server.on('request', (req, res) => {
    const httpProtect = domain.create();

    httpProtect.add(req);
    httpProtect.add(res);

    httpProtect.on('error', (err) => {

        const httpErrProtect = domain.create();
        httpErrProtect.add(req);
        httpErrProtect.add(res);

        httpErrProtect.on('error', (err) => {
            console.error(err);
        });

        httpErrProtect.run(() => {
            res.statusCode = 500;
            res.setHeader('Content-Type', 'text/plain');
            res.end(`${err.message}\n`);
        });
    });

    httpProtect.run(() => {
        throw new Error('test error');

        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Hello World\n');
    });
});

server.listen(3000, '0.0.0.0', () => {
    console.log('server listening');
});


별로 문제가 없어 보이긴 한다. scope는 중첩되는 순간, 새로 선언한 scope로 error 이벤트는 등록된다. 상위 scope의 error 이벤트는 동작하지 않는다. run 함수 중첩사용은 시나리오를 예측해서 작성해야만, 대응이 수월할 것이며, 그렇지 않은 경우, error 이벤트의 호출 시점을 예측 할 수 없다.


'Nodejs' 카테고리의 다른 글

Node.js Package 취약점 분석하기  (0) 2017.08.16
Node.js 설치하기  (0) 2017.03.26
Node.js certificate has expired Error 해결하기  (0) 2016.06.17
Node.js Callback에 관한 고찰  (0) 2016.05.30
Node.js v6 버전 업그레이드 후기  (0) 2016.05.29

+ Recent posts