분류 Nodejs

node.js 리팩토링 (1 부)

컨텐츠 정보

  • 조회 395 (작성일 )

본문

이 글은 보다 깨끗하고 효과적인 node.js 코드를 작성하기 위한 팁을 공유하는 일련의 기사 중 첫 번째 부분입니다.


https://dev.to/paulasantamaria/refactoring-node-js-part-1-42fe 


1. 비동기 / 대기 (async/await) 사용 


따라서 자바 스크립트에서 비동기 코드를 작성하는 방법에는 콜백, 약속 및 비동기 / 대기의 3 가지 방법이 있습니다.


(아직 콜백 지옥을 탈출하지 않은 경우 다른 dev.to 기사 : @amberjones의 JavaScipt 약속으로 콜백 지옥을 탈출하는 방법)을 확인하는 것이 좋습니다.


Async / await를 사용하면 약속보다 더 깨끗하고 읽기 쉬운 구문으로 비동기 비 차단 코드를 작성할 수 있습니다.?


다음 코드는 myFunction()을 실행하고 결과를 반환하며 함수에 의해 발생할 수 있는 오류를 처리하는 예제를 보겠습니다.


// Promises
myFunction()
    .then(data => {
        doStuff(data);
    })
    .catch(err => {
        handle(err);
    });


// async/await
try {
    const data = await myFunction();
    doStuff(data);
}
catch (err) {
    handle(err);
}


async / await로 더 읽기 쉽고 읽기 쉽지 않습니까?


비동기 / 대기에 관한 몇 가지 추가 팁 :

  • 약속을 반환하는 모든 함수를 기다릴 수 있습니다.
  • await 키워드는 비동기 함수 내에서만 사용할 수 있습니다.
  • await Promise.all ([asyncFunction1, asyncFunction2])를 사용하여 비동기 함수를 병렬로 실행할 수 있습니다.

2. Avoid await in loops 


async / await는 매우 깨끗하고 읽기 쉬우므로 다음과 같은 작업을 수행 할 수 있습니다.


const productsToUpdate = await productModel.find({ outdated: true });

for (const key in productsToUpdate) {
    const product = productsToUpdate[key];

    product.outdated = false;
    await product.save();
}


위의 코드는 find를 사용하여 제품 목록을 검색 한 후 제품을 반복하여 하나씩 업데이트합니다. 아마도 효과가 있을 것입니다. 그러나 우리는 더 잘 할 수 있어야 합니다. ?. 다음 대안을 고려하십시오.


옵션 A : 단일 쿼리 작성 


제품을 찾고 하나의 제품으로 모두 업데이트하는 쿼리를 쉽게 작성할 수 있으므로 데이터베이스에 대한 책임을 위임하고 N 작업을 1로 줄입니다. 방법은 다음과 같습니다.


await productModel.update({ outdated: true }, {
    $set: {
        outdated: false
    }
 });


옵션 B : Promise.all 


분명히,이 예제에서 옵션 A는 확실히 갈 길이지만 비동기 작업을 하나로 병합 할 수 없는 경우 (데이터베이스 작업이 아니라 외부 REST API에 대한 요청 일 수 있음), Promise.all을 사용하여 모든 작업을 병렬로 실행하는 것을 고려하십시오.


const firstOperation = myAsyncFunction();
const secondOperation = myAsyncFunction2('test');
const thirdOperation = myAsyncFunction3(5);

await Promise.all([ firstOperation, secondOperation, thirdOperation ]);


이 방법은 모든 비동기 기능을 실행하고 모든 기능이 해결 될 때까지 기다립니다. 작업에 서로 의존성이 없는 경우에만 작동합니다.


3. 비동기 fs 모듈 사용 


Node의 fs 모듈을 사용하면 파일 시스템과 상호 작용할 수 있습니다. fs 모듈의 모든 작업에는 동기 및 비동기 옵션이 포함됩니다.


다음은 파일을 읽는 비동기 및 동기화 코드의 예입니다.?


// Async
fs.readFile(path, (err, data) => {
    if (err)
        throw err;

    callback(data);
});

// Sync 
return fs.readFileSync(path);

동기 옵션 (일반적으로 readFileSync와 같이 Sync로 끝남)은 콜백이 필요하지 않기 때문에 더 깔끔해 보이지만 실제로는 응용 프로그램 성능을 저하시킬 수 있습니다. 왜? 동기화 작업이 차단 중이므로 앱이 파일을 동 기적으로 읽고 다른 코드의 실행을 차단하는 중입니다.


그러나 fs 모듈을 비동기 적으로 사용하고 콜백도 피할 수 있는 방법을 찾는 것이 좋습니다. 방법을 알아 보려면 다음 팁을 확인하십시오.


4. util.promisify를 사용하여 콜백(callbacks)을 약속(promises)으로 변환


promisify는 node.js 유틸리티 모듈의 함수입니다. 표준 콜백 구조를 따르는 함수를 사용하여 약속으로 변환합니다. 또한 콜백 스타일 함수에서 대기를 사용할 수 있습니다.


예를 보자. 노드의 fs 모듈에서 readFile 및 access 함수는 모두 콜백 스타일 구조를 따르므로 await 함수를 사용하여 비동기 함수에서 사용할 수 있습니다.


콜백 버전은 다음과 같습니다.


const fs = require('fs');

const readFile = (path, callback) => {
    // Check if the path exists.
    fs.stat(path, (err, stats) => {
        if (err)
            throw err;

        // Check if the path belongs to a file.
        if (!stats.isFile())
            throw new Error('The path does not belong to a file');

        // Read file.
        fs.readFile(path, (err, data) => {
            if (err)
                throw err;

            callback(data);
        });
    });
}

그리고 여기에 "약속 된"+ 비동기 버전이 있습니다 :


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

const readFilePromise = util.promisify(fs.readFile);
const statPromise = util.promisify(fs.stat);

const readFile = async (path) => {
    // Check if the path exists.
    const stats = await statPromise(path);

    // Check if the path belongs to a file.
    if (!stats.isFile())
        throw new Error('The path does not belong to a file');

    // Read file.
    return await readFilePromise(path);
}


5. 설명 오류 유형 사용 


id로 제품을 반환하는 REST API에 대한 엔드 포인트를 구축한다고 가정 해 봅시다. 서비스는 로직을 처리하고 컨트롤러는 요청을 처리하고 서비스를 호출하고 응답을 빌드합니다.


/* --- product.service.js --- */

const getById = async (id) => {
    const product = await productModel.findById(id);

    if (!product)
        throw new Error('Product not found');

    return product;
}

/* --- product.controller.js --- */

const getById = async (req, res) => {
    try {
        const product = await productService.getById(req.params.id);

        return product;
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
}


그래서, 여기서 무슨 문제가 있습니까? 서비스의 첫 번째 라인 (productModel.findById (id))이 데이터베이스 또는 네트워크 관련 오류를 발생 시키고 이전 코드에서 오류가 "찾을 수 없음"오류와 정확히 동일하게 처리된다고 가정합니다. 이렇게 하면 클라이언트의 오류 처리가 더 복잡해집니다.


또한 더 큰 문제는 보안상의 이유로 클라이언트에게 오류가 반환 되는 것을 원하지 않습니다 (민감한 정보가 노출 될 수 있음).


이 문제를 어떻게 해결합니까? 


이를 처리하는 가장 좋은 방법은 각 경우에 따라 Error 클래스의 다른 구현을 사용하는 것입니다. 이것은 우리 자신의 커스텀 구현을 구축하거나 필요한 Error 구현이 이미 포함 된 라이브러리를 설치함으로써 달성 될 수 있습니다.


REST API의 경우 throw.js를 사용하고 싶습니다. 가장 일반적인 HTTP 상태 코드와 일치하는 오류를 포함하는 정말 간단한 모듈입니다. 이 모듈에서 정의한 각 오류에는 상태 코드가 속성으로 포함됩니다.


throw.js를 사용하여 이전 예제가 어떻게 보이는지 봅시다 :


/* --- product.service.js --- */
const error = require('throw.js');

const getById = async (id) => {
    const product = await productModel.findById(id);

    if (!product)
        throw new error.NotFound('Product not found');

    return product;
}

/* --- product.controller.js --- */
const error = require('throw.js');

const getById = async (req, res) => {
    try {
        const product = await productService.getById(req.params.id);

        return product;
    }
    catch (err) {
        if (err instanceof error.NotFound)
            res.status(err.statusCode).json({ error: err.message });
        else
            res.status(500).json({ error: 'Unexpected error' });
    }
}

이 두 번째 접근 방식에서 우리는 두 가지를 달성했습니다.

  • 컨트롤러에는 이제 오류를 이해하고 그에 따라 행동 할 수 있는 충분한 정보가 있습니다.
  • REST API 클라이언트는 이제 오류를 처리하는 데 도움이 되는 상태 코드도 받습니다.


또한 모든 오류를 처리하는 전역 오류 처리기 또는 미들웨어를 구축하여 컨트롤러에서 해당 코드를 지울 수 있습니다. 그러나 그것은 다른 기사에 대한 것입니다.


가장 일반적인 오류 유형을 구현하는 또 다른 모듈은 다음과 같습니다. node-common-errors.


생각? 


이 팁이 도움이 되었습니까?


이 시리즈의 다음 기사에서 다른 node.js 관련 주제에 대해 쓰시겠습니까?


효과적이고 깨끗한 node.js 코드를 작성하기 위한 팁은 무엇입니까?