Node.js에서 비동기 코드 작성하는 방법

저자는 오픈 인터넷/프리 스피치 기금을(를) 기부 프로그램의 일환으로 선택했습니다.

소개

자바스크립트 프로그램의 많은 경우, 코드는 개발자가 작성한 대로 한 줄씩 실행됩니다. 이를 동기적 실행이라고하며, 이는 줄이 작성된 순서대로 하나씩 실행됩니다. 그러나 컴퓨터에 제공하는 모든 명령이 즉시 처리되어야 하는 것은 아닙니다. 예를 들어, 네트워크 요청을 보내면 코드를 실행하는 프로세스는 데이터가 반환될 때까지 기다려야합니다. 이 경우 네트워크 요청이 완료될 때까지 기다리는 동안 다른 코드를 실행하지 않으면 시간이 낭비됩니다. 이 문제를 해결하기 위해 개발자들은 비동기 프로그래밍을 사용합니다. 여기서 코드 줄은 작성된 순서와 다른 순서로 실행됩니다. 비동기 프로그래밍을 사용하면 네트워크 요청과 같은 오래 걸리는 작업이 완료될 때까지 다른 코드를 실행할 수 있습니다.

자바스크립트 코드는 컴퓨터 프로세스 내에서 단일 스레드에서 실행됩니다. 그 코드는 이 스레드에서 동기적으로 처리되며 한 번에 하나의 명령만 실행됩니다. 따라서 이 스레드에서 긴 시간이 걸리는 작업을 수행한다면 해당 작업이 완료될 때까지 남은 모든 코드가 차단됩니다. 자바스크립트의 비동기 프로그래밍 기능을 활용하여 긴 시간이 걸리는 작업을 백그라운드 스레드로 오프로드하여 이 문제를 피할 수 있습니다. 작업이 완료되면 작업 데이터를 처리해야 하는 코드가 다시 주요 단일 스레드에 올라갑니다.

이 튜토리얼에서는 자바스크립트가 이벤트 루프를 통해 비동기 작업을 어떻게 관리하는지에 대해 배우게 됩니다. 이는 다른 작업을 기다리는 동안 새 작업을 완료하는 자바스크립트 구조입니다. 그런 다음 스튜디오 지브리 API에서 영화 목록을 요청하고 데이터를 CSV 파일에 저장하는 프로그램을 만들 것입니다. 비동기 코드는 콜백, 프로미스, 그리고 async/await 키워드를 사용하여 세 가지 방법으로 작성될 것입니다.

참고: 이 글을 쓰는 시점에서 비동기 프로그래밍은 더 이상 콜백만을 사용하여 수행되지 않지만, 이 구식 방법을 배우는 것은 자바스크립트 커뮤니티가 지금은 프로미스를 사용하는 이유에 대한 맥락을 제공할 수 있습니다. async/await 키워드를 사용하면 프로미스를 덜 수다스럽게 사용할 수 있으며, 따라서 이 기사를 작성하는 시점에서 자바스크립트에서 비동기 프로그래밍을 수행하는 표준 방법입니다.

전제 조건

이벤트 루프

자바스크립트 함수 실행의 내부 작동 방식을 연구하여 시작합시다. 이러한 동작을 이해하면 비동기 코드를 보다 의도적으로 작성할 수 있으며, 향후 코드 문제 해결에도 도움이 됩니다.

자바스크립트 인터프리터가 코드를 실행할 때 호출되는 모든 함수는 자바스크립트의 호출 스택에 추가됩니다. 호출 스택은 스택입니다. 이는 항목이 맨 위에만 추가되고 맨 위에서만 제거되는 리스트와 유사한 데이터 구조입니다. 스택은 “마지막에 들어온 것이 먼저 나가는” 또는 LIFO 원칙을 따릅니다. 스택에 두 개의 항목을 추가하면 가장 최근에 추가된 항목이 먼저 제거됩니다.

호출 스택을 사용하여 예를 들어보겠습니다. 자바스크립트가 호출되는 함수 functionA()를 만나면 호출 스택에 추가됩니다. 그 함수 functionA()가 다른 함수 functionB()를 호출하면 functionB()가 스택 맨 위에 추가됩니다. 자바스크립트가 함수의 실행을 완료하면 스택에서 제거됩니다. 따라서 자바스크립트는 functionB()를 먼저 실행하고 완료되면 스택에서 제거한 다음 functionA()의 실행을 마치고 호출 스택에서 제거합니다. 이것이 내부 함수가 항상 외부 함수보다 먼저 실행되는 이유입니다.

JavaScript이 비동기 작업을 만나면 파일에 쓰기와 같은 작업을 메모리의 테이블에 추가합니다. 이 테이블은 작업, 완료되어야 하는 조건 및 완료될 때 호출될 함수를 저장합니다. 작업이 완료되면 JavaScript은 연결된 함수를 메시지 대기열에 추가합니다. 대기열은 항목을 하단에만 추가할 수 있지만 맨 위에서만 제거할 수 있는 또 다른 리스트 형식의 데이터 구조입니다. 메시지 대기열에서 두 개 이상의 비동기 작업이 함수 실행을 준비하면 먼저 완료된 비동기 작업의 함수가 먼저 실행되도록 표시됩니다.

메시지 대기열에 있는 함수들은 호출 스택에 추가되기를 기다립니다. 이벤트 루프는 호출 스택이 비어 있는지를 반복적으로 확인하는 과정입니다. 비어 있다면 메시지 대기열의 첫 번째 항목이 호출 스택으로 이동합니다. JavaScript는 코드에서 해석하는 함수 호출보다 메시지 대기열의 함수를 우선시합니다. 호출 스택, 메시지 대기열 및 이벤트 루프의 결합 효과로 JavaScript 코드를 처리하면서 비동기 활동을 관리할 수 있습니다.

이벤트 루프의 고수준 이해를 갖게 되었으므로 작성한 비동기 코드가 어떻게 실행될지 알 수 있습니다. 이 지식을 바탕으로 콜백, 프로미스 및 async/await를 사용하여 세 가지 다른 접근 방식으로 비동기 코드를 생성할 수 있습니다.

콜백을 사용한 비동기 프로그래밍

A callback function is one that is passed as an argument to another function, and then executed when the other function is finished. We use callbacks to ensure that code is executed only after an asynchronous operation is completed.

오랫동안 콜백은 비동기 코드를 작성하는 가장 일반적인 메커니즘이었지만, 이제 코드를 읽기 어렵게 만들 수 있기 때문에 대부분 구식이 되었습니다. 이 단계에서는 콜백을 사용하여 비동기 코드의 예제를 작성하여 다른 전략의 효율성이 향상되는 것을 확인할 수 있습니다.

다른 함수에서 콜백 함수를 사용하는 여러 가지 방법이 있습니다. 일반적으로 이 구조를 취합니다:

function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {
    [ Action ]
}

자바스크립트나 노드.js에서 외부 함수의 마지막 인수로 콜백 함수를 가져야 하는 구문적 요구사항은 아니지만, 콜백을 더 쉽게 식별할 수 있도록 하는 일반적인 관행입니다. 또한 자바스크립트 개발자들이 콜백으로 익명 함수를 사용하는 것이 일반적입니다. 익명 함수는 이름 없이 생성된 함수입니다. 함수가 인수 목록의 끝에 정의될 때 보통 훨씬 더 가독성이 좋습니다.

콜백을 시연하기 위해 스튜디오 지브리 영화 목록을 파일에 작성하는 Node.js 모듈을 만들어 보겠습니다. 먼저 JavaScript 파일과 해당 출력을 저장할 폴더를 만듭니다:

  1. mkdir ghibliMovies

그런 다음 해당 폴더로 이동합니다:

  1. cd ghibliMovies

먼저 콜백 함수가 결과를 기록할 스튜디오 지브리 API로 HTTP 요청을 시작합니다. 이를 위해 HTTP 응답 데이터에 액세스할 수 있도록 라이브러리를 설치할 것입니다.

터미널에서 npm을 초기화하여 나중에 패키지에 대한 참조를 가질 수 있도록 합니다:그런 다음 request 라이브러리를 설치합니다:

  1. npm init -y

그런 다음, request 라이브러리를 설치하세요:

  1. npm i request --save

이제 nano와 같은 텍스트 에디터에서 callbackMovies.js라는 새 파일을 엽니다:

  1. nano callbackMovies.js

텍스트 에디터에서 다음과 같은 코드를 입력하세요. 먼저 request 모듈을 사용하여 HTTP 요청을 보내기 시작합니다:

callbackMovies.js
const request = require('request');

request('https://ghibliapi.herokuapp.com/films');

첫 번째 줄에서는 npm을 통해 설치된 request 모듈을 로드합니다. 모듈은 HTTP 요청을 보낼 수 있는 함수를 반환하고, 그 함수를 request 상수에 저장합니다.

그런 다음 request() 함수를 사용하여 HTTP 요청을 보냅니다. 이제 강조 표시된 변경 사항을 추가하여 HTTP 요청의 데이터를 콘솔에 출력합니다:

callbackMovies.js
const request = require('request');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    movies.forEach(movie => {
        console.log(`${movie['title']}, ${movie['release_date']}`);
    });
});

request() 함수를 사용할 때 두 개의 매개변수를 제공합니다:

  • 요청하려는 웹사이트의 URL
  • A callback function that handles any errors or successful responses after the request is complete

콜백 함수에는 error, response, body라는 세 가지 인수가 있습니다. HTTP 요청이 완료되면 인수는 결과에 따라 자동으로 값을 할당받습니다. 요청이 전송되지 못하면 error에 객체가 포함되지만 responsebodynull입니다. 요청이 성공적으로 전송되면 HTTP 응답이 response에 저장됩니다. 우리의 HTTP 응답이 데이터를 반환하면(이 예제에서는 JSON을 받습니다) 데이터는 body에 설정됩니다.

우리의 콜백 함수는 먼저 오류를 받았는지 확인합니다. 콜백에서 오류를 먼저 확인하는 것이 가장 좋습니다. 그렇게 함으로써 콜백의 실행이 누락된 데이터와 함께 계속되지 않습니다. 이 경우에는 오류와 함수의 실행을 기록합니다. 그런 다음 응답의 상태 코드를 확인합니다. 우리의 서버는 항상 사용 가능하지 않을 수 있으며, API가 변경되어 한 번 합리적인 요청이 잘못된 것으로 바뀔 수 있습니다. 상태 코드가 200인지 확인함으로써 요청이 “OK”였음을 확인하여 응답이 예상대로인지 확신할 수 있습니다.

마지막으로 응답 본문을 Array로 구문 분석하고 각 영화를 확인하여 이름과 개봉 연도를 기록합니다.

파일을 저장하고 종료한 후에는 다음 명령으로 이 스크립트를 실행하십시오:

  1. node callbackMovies.js

다음 출력이 표시됩니다:

Output
Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014

우리는 스튜디오 지브리 영화 목록과 개봉 연도를 성공적으로 받았습니다. 이제 현재 기록하고 있는 영화 목록을 파일로 작성하여이 프로그램을 완성해 보겠습니다.

텍스트 편집기에서 callbackMovies.js 파일을 업데이트하여 다음과 같이 강조된 코드를 추가하십시오. 이 코드는 영화 데이터로 CSV 파일을 작성합니다:

callbackMovies.js
const request = require('request');
const fs = require('fs');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    let movieList = '';
    movies.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });

    fs.writeFile('callbackMovies.csv', movieList, (error) => {
        if (error) {
            console.error(`Could not save the Ghibli movies to a file: ${error}`);
            return;
        }

        console.log('Saved our list of movies to callbackMovies.csv');;
    });
});

강조된 변경 사항을 주목하면 fs 모듈을 가져 왔음을 알 수 있습니다. 이 모듈은 모든 Node.js 설치에 표준으로 포함되어 있으며 파일에 비동기적으로 쓸 수 있는 writeFile() 메서드를 포함하고 있습니다.

대신 콘솔에 데이터를 기록하는 대신에, 우리는 이를 문자열 변수 movieList에 추가합니다. 그런 다음 movieList의 내용을 새 파일 callbackMovies.csv에 저장하기 위해 writeFile()를 사용합니다. 마지막으로, 우리는 writeFile() 함수에 콜백을 제공합니다. 이 콜백은 하나의 인수 error를 갖습니다. 이를 통해 파일에 쓸 수 없는 경우를 처리할 수 있습니다. 예를 들어 사용자가 node 프로세스를 실행하는 데 필요한 권한을 갖고 있지 않은 경우 등.

파일을 저장하고 이 Node.js 프로그램을 다시 실행하십시오:

  1. node callbackMovies.js

ghibliMovies 폴더에서 callbackMovies.csv를 보게 될 것입니다. 그 파일의 내용은 다음과 같습니다:

callbackMovies.csv
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

중요한 점은 HTTP 요청의 콜백에서 CSV 파일에 쓴다는 것입니다. 코드가 콜백 함수 내부에 있을 때, HTTP 요청이 완료된 후에만 파일에 쓸 것입니다. CSV 파일을 쓴 후에 데이터베이스에 통신하려면 writeFile()의 콜백에서 호출되는 다른 비동기 함수를 만들어야 합니다. 비동기 코드가 많을수록 콜백 함수가 중첩되어야 합니다.

다섯 개의 비동기 작업을 실행하려고 한다고 상상해 봅시다. 각 작업은 다른 작업이 완료된 후에만 실행될 수 있습니다. 이를 코드로 구현한다면 다음과 같을 것입니다:

doSomething1(() => {
    doSomething2(() => {
        doSomething3(() => {
            doSomething4(() => {
                doSomething5(() => {
                    // 최종 작업
                });
            });
        }); 
    });
});

중첩된 콜백이 실행할 코드 라인이 많을 때, 그것들은 상당히 더 복잡하고 가독성이 떨어집니다. JavaScript 프로젝트가 규모와 복잡성이 커짐에 따라 이 효과는 점점 더 강조될 것이며, 결국에는 관리하기 어려워집니다. 이로 인해 개발자들은 더 이상 비동기 작업을 처리하기 위해 콜백을 사용하지 않습니다. 비동기 코드의 구문을 개선하기 위해 우리는 대신 프로미스를 사용할 수 있습니다.

간결한 비동기 프로그래밍을 위한 프로미스 사용

A promise is a JavaScript object that will return a value at some point in the future. Asynchronous functions can return promise objects instead of concrete values. If we get a value in the future, we say that the promise was fulfilled. If we get an error in the future, we say that the promise was rejected. Otherwise, the promise is still being worked on in a pending state.

프로미스는 일반적으로 다음과 같은 형식을 취합니다:

promiseFunction()
    .then([ Callback Function for Fulfilled Promise ])
    .catch([ Callback Function for Rejected Promise ])

이 템플릿에 표시된대로 프로미스는 콜백 함수도 사용합니다. 프로미스가 이행될 때 실행되는 then() 메서드의 콜백 함수가 있습니다. 또한 프로미스가 실행되는 동안 발생하는 모든 오류를 처리하기 위한 catch() 메서드의 콜백 함수도 있습니다.

프로미스를 사용하여 직접적인 경험을 살펴보기 위해 Studio Ghibli 프로그램을 프로미스를 사용하도록 다시 작성해 봅시다.

Axios는 JavaScript용 프로미스 기반 HTTP 클라이언트이므로, 이제 설치해 봅시다:

  1. npm i axios --save

선택한 텍스트 편집기로 promiseMovies.js라는 새 파일을 생성하세요:

  1. nano promiseMovies.js

우리의 프로그램은 axios로 HTTP 요청을 만들고, 그런 다음 새 CSV 파일에 저장하기 위한 특별한 프로미스 기반 버전의 fs를 사용할 것입니다.

다음 코드를 promiseMovies.js에 입력하여 Axios를로드하고 영화 API에 HTTP 요청을 보낼 수 있습니다:

promiseMovies.js
const axios = require('axios');

axios.get('https://ghibliapi.herokuapp.com/films');

첫 번째 줄에서는 반환된 함수를 axios라는 상수에 저장하여 axios 모듈을로드합니다. 그런 다음 axios.get() 메서드를 사용하여 API에 HTTP 요청을 보냅니다.

axios.get() 메서드는 프로미스를 반환합니다. 프로미스를 연결하여 콘솔에 지브리 영화 목록을 인쇄합시다:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        response.data.forEach(movie => {
            console.log(`${movie['title']}, ${movie['release_date']}`);
        });
    })

무슨 일이 일어나고 있는지 살펴보겠습니다. axios.get()로 HTTP GET 요청을 보낸 후에는 프로미스가 이행될 때만 실행되는 then() 함수를 사용합니다. 이 경우에는 콜백 예제와 마찬가지로 화면에 영화를 인쇄합니다.

이 프로그램을 개선하려면 HTTP 데이터를 파일에 쓰는 강조된 코드를 추가하십시오:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })

또한 fs 모듈을 다시 한 번 가져옵니다. fs 가져오기 후에 .promises가 있는지 확인하십시오. Node.js에는 콜백 기반 fs 라이브러리의 프로미스 기반 버전이 포함되어 있으므로 기존 프로젝트에서 역 호환성이 깨지지 않습니다.

이제 HTTP 요청을 처리하는 첫 번째 then() 함수는 콘솔에 인쇄하는 대신 fs.writeFile()을 호출합니다. 프로미스 기반 버전의 fs를 가져 왔으므로 writeFile() 함수가 또 다른 프로미스를 반환합니다. 따라서 writeFile() 프로미스가 이행 될 때를 위해 또 다른 then() 함수를 추가합니다.

A promise can return a new promise, allowing us to execute promises one after the other. This paves the way for us to perform multiple asynchronous operations. This is called promise chaining, and it is analogous to nesting callbacks. The second then() is only called after we successfully write to the file.

참고: 이 예에서는 콜백 예제에서와 같이 HTTP 상태 코드를 확인하지 않았습니다. 기본적으로 axios는 오류를 나타내는 상태 코드를 받으면 프로미스를 이행하지 않습니다. 따라서 더 이상 이를 확인할 필요가 없습니다.

이 프로그램을 완료하려면 다음과 같이 프로미스를 catch() 함수로 체이닝하십시오:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })
    .catch((error) => {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    });

프로미스 체인에서 어떤 프로미스도 이행되지 않으면 JavaScript는 자동으로 정의된 경우 catch() 함수로 이동합니다. 이것이 두 개의 비동기 작업이 있음에도 불구하고 하나의 catch() 절만 있는 이유입니다.

다음을 실행하여 프로그램이 동일한 출력을 생성하는지 확인하십시오:

  1. node promiseMovies.js

ghibliMovies 폴더에서 다음을 포함하는 promiseMovies.csv 파일이 표시됩니다:

promiseMovies.csv
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

프로미스를 사용하면 콜백만 사용하는 것보다 훨씬 간결한 코드를 작성할 수 있습니다. 프로미스 체인의 콜백은 중첩된 콜백보다 깔끔한 옵션입니다. 그러나 더 많은 비동기 호출을 하면 프로미스 체인이 더 길고 유지하기 어려워집니다.

콜백과 프로미스의 장황함은 비동기 작업의 결과가 있는 경우 함수를 생성해야 한다는 필요성에서 나옵니다. 더 좋은 경험은 비동기 결과를 기다리고 함수 외부의 변수에 넣는 것입니다. 이렇게 하면 함수를 만들지 않고도 변수에서 결과를 사용할 수 있습니다. 이를 asyncawait 키워드로 달성할 수 있습니다.

JavaScript로 async/await 작성하기

async/await 키워드는 프로미스와 작업할 때 대안적인 구문을 제공합니다. then() 메서드에서 프로미스의 결과를 사용할 대신, 결과는 다른 함수와 마찬가지로 값으로 반환됩니다. 우리는 async 키워드로 함수를 정의하여 JavaScript에게 이것이 프로미스를 반환하는 비동기 함수임을 알립니다. 우리는 await 키워드를 사용하여 프로미스가 충족될 때 프로미스 자체를 반환하는 대신 프로미스의 결과를 반환하도록 JavaScript에게 알립니다.

일반적으로 async/await 사용법은 다음과 같습니다:

async function() {
    await [Asynchronous Action]
}

async/await를 사용하여 Studio Ghibli 프로그램을 어떻게 개선할 수 있는지 살펴봅시다. 텍스트 편집기를 사용하여 새 파일 asyncAwaitMovies.js를 만들고 엽니다:

  1. nano asyncAwaitMovies.js

새로 열린 JavaScript 파일에서 약속 예제에서 사용한 모듈을 가져오는 것으로 시작해 봅시다:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

가져온 것들은 promiseMovies.js와 같습니다. 왜냐하면 async/await는 프로미스를 사용하기 때문입니다.

이제 비동기 코드가 있는 함수를 만들기 위해 async 키워드를 사용합니다:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {}

새로운 함수를 만들어서 saveMovies()라고 하지만 그 정의의 시작에 async를 포함합니다. 이것은 우리가 비동기 함수에서만 await 키워드를 사용할 수 있기 때문에 중요합니다.

Ghibli API에서 영화 목록을 가져오는 HTTP 요청을 만들기 위해 await 키워드를 사용하세요:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
}

saveMovies() 함수에서는 이전과 같이 axios.get()으로 HTTP 요청을 만듭니다. 이번에는 then() 함수와 연결하지 않습니다. 대신 호출하기 전에 axios.get() 앞에 await를 추가합니다. JavaScript가 await를 보면 axios.get()의 실행이 완료되고 response 변수가 설정된 후에 함수의 남은 코드를 실행합니다. 다른 코드는 영화 데이터를 저장하여 파일에 쓸 수 있습니다.

영화 데이터를 파일에 씁시다:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
    await fs.writeFile('asyncAwaitMovies.csv', movieList);
}

fs.writeFile()로 파일에 쓸 때도 await 키워드를 사용합니다.

이 함수를 완료하기 위해 프로미스가 발생할 수 있는 오류를 처리해야 합니다. 우리의 코드를 try/catch 블록으로 묶어서 이를 수행합시다:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

프로미스가 실패할 수 있기 때문에 비동기 코드를 try/catch 절로 감쌉니다. 이렇게 하면 HTTP 요청 또는 파일 쓰기 작업이 실패할 때 throw되는 모든 오류를 잡을 수 있습니다.

마지막으로 비동기 함수인 saveMovies()를 호출하여 프로그램을 node로 실행할 때 실행되도록 합니다.

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

saveMovies();

한눈에 보면 이는 전형적인 동기식 JavaScript 코드 블록처럼 보입니다. 함수 전달이 더 적어서 조금 더 깔끔해 보입니다. 이러한 작은 조정은 async/await와 함께 비동기 코드를 유지보수하기 쉽게 만듭니다.

터미널에 다음을 입력하여 프로그램의 이번 버전을 테스트하세요:

  1. node asyncAwaitMovies.js

ghibliMovies 폴더에서 다음 내용이 포함된 새로운 asyncAwaitMovies.csv 파일이 생성됩니다:

asyncAwaitMovies.csv
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014

이제 JavaScript 기능 async/await를 사용하여 비동기 코드를 관리했습니다.

결론

이 튜토리얼에서는 JavaScript가 함수를 실행하고 이벤트 루프를 사용하여 비동기 작업을 관리하는 방법을 배웠습니다. 그런 다음 HTTP 요청을 만든 후 다양한 비동기 프로그래밍 기술을 사용하여 영화 데이터를 위한 CSV 파일을 작성하는 프로그램을 작성했습니다. 먼저 사용한 것은 더 이상 사용되지 않는 콜백 기반 접근 방식이었습니다. 그런 다음 프로미스를 사용했고 마지막으로 프로미스 구문을 더 간결하게 만드는 async/await를 사용했습니다.

Node.js로 비동기 코드를 이해하면 API 호출에 의존하는 프로그램을 개발할 수 있습니다. 이 공개 API 목록을 살펴보세요. 이를 사용하려면 이 튜토리얼에서 했던 것처럼 비동기 HTTP 요청을 수행해야 합니다. 더 깊이있는 공부를 위해 이러한 API를 사용하는 앱을 만들어 보세요. 여기서 배운 기술을 연습할 수 있습니다.

Source:
https://www.digitalocean.com/community/tutorials/how-to-write-asynchronous-code-in-node-js