Node.js 작동 방법

완료됨

이 단원에서는 Node.js가 JavaScript 런타임으로 들어오는 작업을 처리하는 방법을 설명합니다.

태스크 유형

JavaScript 애플리케이션에는 두 가지 형식의 작업이 있습니다.

  • 동기 작업: 이러한 작업은 순서대로 발생합니다. 완료하기 위해 다른 리소스에 종속되지 않습니다. 예로는 수학 연산이나 문자열 조작이 있습니다.
  • 비동기: 이러한 작업은 다른 리소스에 종속되어 있으므로 즉시 완료되지 않을 수 있습니다. 예로는 네트워크 요청 또는 파일 시스템 작업이 있습니다.

프로그램이 가능한 한 빨리 실행되기를 원하기 때문에 JavaScript 엔진이 비동기 작업의 응답을 기다리는 동안 계속 작업할 수 있도록 해야 합니다. 이를 위해 비동기 작업을 작업 큐에 추가하고 다음 작업을 계속 진행합니다.

이벤트 루프를 사용하여 작업 큐 관리

Node.js는 JavaScript 엔진의 이벤트 기반 아키텍처를 사용하여 비동기 요청을 처리합니다. 다음 다이어그램은 V8 이벤트 루프가 상위 레벨에서 작동하는 방식을 보여줍니다.

Diagram showing how Node J S uses an event-driven architecture where an event loop processes events and returns callbacks.

적절한 구문(아래 참조)으로 표시되는 비동기 작업이 이벤트 루프에 추가됩니다. 작업에는 수행할 작업과 결과를 수신하는 콜백 함수가 포함됩니다. 집약적인 작업이 완료되면 콜백 함수가 결과와 함께 트리거됩니다.

동기 작업과 비동기 작업

Node.js API는 파일 작업과 같은 몇몇 동일한 작업에 대해 동기 작업과 비동기 작업을 모두 제공합니다. 일반적으로는 항상 비동기 우선이라고 생각해야 하지만 때로는 동기 작업을 사용할 수도 있습니다.

그 예로 CLI(명령줄 인터페이스)가 파일을 읽은 다음 파일의 데이터를 즉시 사용하는 경우를 들 수 있습니다. 이 경우 애플리케이션을 사용하기 위해 대기하는 다른 시스템이나 사람이 없기 때문에 파일 작업의 동기 버전을 사용할 수 있습니다.

그러나 웹 서버를 빌드하는 경우에는 다른 사용자 요청을 처리하는 단일 스레드의 실행 기능을 차단하지 않기 위해 항상 비동기 버전의 파일 작업을 사용해야 합니다.

TailWind Traders의 개발자로 작업할 때 동기 작업과 비동기 작업의 차이점과 각 작업을 사용하는 경우를 이해해야 합니다.

비동기 작업을 통한 성능

Node.js 서버 작업을 빠르고 고성능으로 구성하는 JavaScript의 고유한 이벤트 기반 특성을 활용합니다. JavaScript는 비동기 기술로 올바르게 사용될 때 V8 엔진에 의해 이루어지는 성능 향상으로 인해 C와 같은 저수준 언어와 동일한 성능 결과를 생성할 수 있습니다.

비동기 기술은 3가지 스타일로 제공되며, 이러한 스타일을 작업에서 인식할 수 있어야 합니다.

  • 비동기/대기(권장): 비동기 작업의 결과를 수신하기 위해 키워드(keyword) 사용하는 async await 최신 비동기 기술입니다. 비동기/대기는 여러 프로그래밍 언어에 걸쳐 사용됩니다. 일반적으로 새 종속성을 가진 새 프로젝트는 이 스타일의 비동기 코드를 사용합니다.
  • 콜백: 콜백 함수를 사용하여 비동기 작업의 결과를 수신하는 원래 비동기 기술입니다. 이전 코드 베이스 및 이전 Node.js API에서 볼 수 있습니다.
  • 프라미스: 프라미스 개체를 사용하여 비동기 작업의 결과를 수신하는 새로운 비동기 기술입니다. 최신 코드 베이스 및 최신 Node.js API에서 볼 수 있습니다. 업데이트되지 않는 이전 API를 래핑하려면 작업에서 프라미스 기반 코드를 작성해야 할 수도 있습니다. 이 래핑에 프라미스를 사용하여 최신 비동기/대기 스타일의 코드보다 더 많은 범위의 Node.js 버전 프로젝트에서 코드를 사용할 수 있습니다.

async/await

Async/await는 비동기 프로그래밍을 처리하는 최신 방법입니다. 비동기/대기는 프라미스 위에 얹힌 장식 같은 구문으로 비동기 코드를 동기 코드처럼 보이게 합니다. 또한 더 쉽게 읽고 기본.

async/await를 사용한 동일한 예는 다음과 같습니다.

// async/await asynchronous example

const fs = require('fs').promises;

const filePath = './file.txt';

// `async` before the parent function
async function readFileAsync() {
  try {
    // `await` before the async method
    const data = await fs.readFile(filePath, 'utf-8');
    console.log(data);
    console.log('Done!');
  } catch (error) {
    console.log('An error occurred...: ', error);
  }
}

readFileAsync()
  .then(() => {
    console.log('Success!');
  })
  .catch((error) => {
    console.log('An error occurred...: ', error);
  });

ES2017에서 비동기/await가 릴리스되었을 때 키워드(keyword) 최상위 함수가 프라미스인 함수에서만 사용할 수 있었습니다. 약속과 섹션을 가질 then catch 필요는 없었지만 여전히 구문을 실행해야 promise 했습니다.

함수는 async 내부에 호출이 없더라도 항상 프라미스를 await 반환합니다. promise는 함수에서 반환된 값으로 확인됩니다. 함수가 오류를 throw하면 throw된 값으로 프라미스가 거부됩니다.

프라미스

중첩된 콜백은 읽고 관리하기가 어려울 수 있기 때문에 Node.js에서 프라미스에 대한 지원을 추가했습니다. 프라미스는 비동기 작업의 최종 완료(또는 실패)를 나타내는 개체입니다.

프라미스 함수의 형식은 다음과 같습니다.

// Create a basic promise function
function promiseFunction() {
  return new Promise((resolve, reject) => {
    // do something

    if (error) {
      // indicate success
      reject(error);
    } else {
      // indicate error
      resolve(data);
    }
  });
}

// Call a basic promise function
promiseFunction()
  .then((data) => {
    // handle success
  })
  .catch((error) => {
    // handle error
  });

프라미스가 이행되면 then 메서드가 호출되고 약속이 거부되면 catch 메서드가 호출됩니다.

프라미스를 사용하여 파일을 비동기적으로 읽는 코드는 다음과 같습니다.

// promises asynchronous example

const fs = require('fs').promises;
const filePath = './file.txt';

// request to read a file
fs.readFile(filePath, 'utf-8')
  .then((data) => {
    console.log(data);
    console.log('Done!');
  })
  .catch((error) => {
    console.log('An error occurred...: ', error);
  });

console.log(`I'm the last line of the file!`);

최상위 비동기/대기

Node.js의 최신 버전에서는 ES6 모듈에 대한 최상위 비동기/대기를 추가했습니다. 이 기능을 사용하려면 package.jsontype(이)라는 속성을 module 값으로 추가해야 합니다.

{
    "type": "module"
}

그런 다음 최상위 수준 코드에서 await 키워드를 사용할 수 있습니다.

// top-level async/await asynchronous example

const fs = require('fs').promises;

const filePath = './file.txt';

// `async` before the parent function
try {
  // `await` before the async method
  const data = await fs.readFile(filePath, 'utf-8');
  console.log(data);
  console.log('Done!');
} catch (error) {
  console.log('An error occurred...: ', error);
}
console.log("I'm the last line of the file!");

콜백

Node.js가 처음 릴리스되었을 때 비동기 프로그래밍은 콜백 함수를 사용하여 처리되었습니다. 콜백은 다른 함수에 인수로 전달되는 함수입니다. 작업이 완료되면 콜백 함수가 호출됩니다.

함수의 매개 변수 순서가 중요합니다. 콜백 함수가 함수의 마지막 매개 변수입니다.

// Callback function is the last parameter
function(param1, param2, paramN, callback)

유지 관리하는 코드의 함수 이름이 callback(으)로 호출되지 않을 수도 있습니다. cb 또는 done 또는 next(으)로 호출될 수 있습니다. 함수의 이름은 중요하지 않지만 매개 변수의 순서는 중요합니다.

함수가 비동기임을 나타내는 구문 표시는 없습니다. 설명서를 읽거나 계속 코드를 읽음으로써 함수가 비동기적이라는 것을 알아야 합니다.

명명된 콜백 함수를 사용한 콜백의 예

다음 코드는 콜백에서 비동기 함수를 구분합니다. 쉽게 읽고 이해할 수 있으며 다른 비동기 함수에 콜백을 다시 사용할 수 있습니다.

// callback asynchronous example

// file system module from Node.js
const fs = require('fs');

// relative path to file
const filePath = './file.txt';

// callback
const callback = (error, data) => {
  if (error) {
    console.log('An error occurred...: ', error);
  } else {
    console.log(data); // Hi, developers!
    console.log('Done!');
  }
};

// async request to read a file
//
// parameter 1: filePath
// parameter 2: encoding of utf-8
// parmeter 3: callback function
fs.readFile(filePath, 'utf-8', callback);

console.log("I'm the last line of the file!");

올바른 결과는 다음과 같습니다.

I'm the last line of the file!
Hi, developers!
Done!

먼저 비동기 함수 fs.readFile이(가) 시작되어 이벤트 루프로 이동합니다. 그런 다음, 코드 실행이 마지막 console.log 코드 줄인 다음 코드 줄로 계속 진행됩니다. 파일을 읽은 후 콜백 함수가 호출되고 두 개의 console.log 문이 실행됩니다.

익명 함수를 사용한 콜백의 예

다음 예는 익명 콜백 함수를 사용합니다. 이는 함수에 이름이 없고, 다른 익명 함수에서 이 함수를 다시 사용할 수 없음을 의미합니다.

// callback asynchronous example

// file system module from Node.js
const fs = require('fs');

// relative path to file
const filePath = './file.txt';

// async request to read a file
//
// parameter 1: filePath
// parameter 2: encoding of utf-8
// parmeter 3: callback function () => {}
fs.readFile(filePath, 'utf-8', (error, data) => {
  if (error) {
    console.log('An error occurred...: ', error);
  } else {
    console.log(data); // Hi, developers!
    console.log('Done!');
  }
});

console.log("I'm the last line of the file!");

올바른 결과는 다음과 같습니다.

I'm the last line of the file!
Hi, developers!
Done!

코드가 실행되면 비동기 함수 fs.readFile이(가) 시작되고 이벤트 루프로 이동합니다. 그런 다음, 실행이 마지막 console.log 코드 줄인 다음 코드 줄로 계속 진행됩니다. 파일을 읽으면 콜백 함수가 호출되고 두 개의 console.log 문이 실행됩니다.

중첩된 콜백

후속 비동기 콜백을 호출한 다음 또 다른 콜백을 호출해야 할 수도 있으므로 콜백 코드가 중첩될 수 있습니다. 이는 콜백 지옥이라 불리며 읽고 관리하기가 어렵습니다.

// nested callback example

// file system module from Node.js
const fs = require('fs');

fs.readFile(param1, param2, (error, data) => {
  if (!error) {
    fs.writeFile(paramsWrite, (error, data) => {
      if (!error) {
        fs.readFile(paramsRead, (error, data) => {
          if (!error) {
            // do something
          }
        });
      }
    });
  }
});

동기식 API

Node.js에는 동기 API 집합도 있습니다. 이러한 API는 작업이 완료될 때까지 프로그램 실행을 차단합니다. 동기 API는 파일을 읽은 다음 파일의 데이터를 즉시 사용하려는 경우에 유용합니다.

Node.js의 동기(차단) 함수는 functionSync의 명명 규칙을 사용합니다. 예를 들어, 비동기식 readFile API에는 readFileSync라는 동기식 API가 있습니다. 코드를 쉽게 읽고 이해할 수 있도록 고유의 프로젝트에서 이 표준을 유지해야 합니다.

// synchronous example

const fs = require('fs');

const filePath = './file.txt';

try {
  // request to read a file
  const data = fs.readFileSync(filePath, 'utf-8');
  console.log(data);
  console.log('Done!');
} catch (error) {
  console.log('An error occurred...: ', error);
}

TailWind Traders의 새 개발자로서 모든 유형의 Node.js 코드를 수정하라는 요청을 받을 수 있습니다. 동기 API와 비동기 API의 차이점과 비동기 코드에 대한 다양한 구문을 이해하는 것이 중요합니다.