본문 바로가기

[JS] Node.js 내장 모듈인 fs 모듈로 비동기 실습 - 2/2 (활용)

[JS] Node.js 내장 모듈인 fs 모듈로 비동기 실습 - 2/2 (활용)

fs.readFile 사용하기

이전 포스팅 참고

 

브라우저 환경과는 다르게 Node.js 환경은 로컬 컴퓨터에서 직접 실행되므로, 파일을 불러오거나 저장하는 등의 액션이 가능하다. fs(File System) module 사용법을 잘 익힌다면 "파일 열기", "파일 저장하기" 등을 직접 구현할 수 있다. Node.js에서는 파일을 읽는 비동기 함수를 제공한다. 이를 통해 callback과 Promise를 구현해보자.

 

* 참고 : 파일을 불러오는 메서드인 fs.readFile의 공식 API 문서

 

// [커맨드] part-2/01_callBack.js 파일을 실행한다.
node part-2/01_callback.js

 

ㄴ 결과 화면은 다음과 같이 나와야 한다.

우리가 읽어와야 할 파일 README.md 의 내용
README.md 의 내용이 터미널에 잘 출력이 된다.

 


 

 

1. callback 으로 구현하기 

 

// fs.readFile등의 메소드를 사용을 위한 파일 시스템 모듈을 불러옴. 
const { readFile } = require("fs");                     
 
const getDataFromFile = function (filePath, callback) {

  readFile(filePath, "utf8", function (err, text) {
  
// 조회에 성공하면 그 내용이 data 인자에 담기고, 조회할 수 없는 경우 익명함수 err를 던질 수 있음
// 조회에 실패한 경우 data 는 null 이고, err 를 첫 번째 인자로 하여 callback 호출   
    if (err) {                                 //?? err 값이 truthy?
      callback(err, null); 
    } else {
// 조회에 성공한 경우 err 는 null 이고, data 를 두 번째 인자로 하여 callback 호출
// 위에서 "utf8" 인코딩을 안 했을 때는 callback(null, test.toString())
      callback(null, text);      
    }
  });
};


// 첫 번째 매개변수에 에러가 들어오면 작동하지 않게 관습적으로 코드를 짜준다.
getDataFromFile('README.md', (err, data) => console.log(data)); 

module.exports = {
  getDataFromFile
};
더보기
const fs = require("fs");                              // (1) fs로 불러옴.
// getDataFromFile함수(이하 get함수) 가 전달받는 2개의 인자 확인

const getDataFromFile = function (filePath, callback) {

  // filePath 에 있는 파일을 utf-8 형식으로 조회한다.
  // 조회에 성공하면 그 내용이 data 인자에 담기고, 조회할 수 없는 경우 익명함수 err를 던질 수 있음
  fs.readFile(filePath, "utf8", (err, data) => {
    if (data === undefined) {

      // 조회에 실패한 경우 data 는 null 이고, err 를 첫 번째 인자로 하여 callback 호출
      callback(err, null);
    } else {
      // 조회에 성공한 경우 err 는 null 이고, data 를 두 번째 인자로 하여 callback 호출
      callback(null, data);
    }
  });};


// "README.md" 가 filePath, 화살표함수가 callback 함수
getDataFromFile('README.md', (err, data) => 
{
  // 이하의 함수내용이 callback 인자로 넘어감
  if (err) {
    console.log(err);
  } else {
    console.log(data);
  }
});

module.exports = {
  getDataFromFile
};

 

2. promissConstructor로 구현하기 

 

mdn을 참고해보면 then 메서드와 catch 메서드는 프로미스를 반환하기때문에 명시적으로 프로미스를 반환하는 then 코드를 작성하지 않아도 프로미스 후속메서드의 체이닝이 가능하다. 

 

callback 매개변수(parameter) 대신, Promise의 resolve, reject 함수를 이용 
const { readFile } = require("fs");

const getDataFromFilePromise = filePath => {
  // filePath 열람 성공 시 .then 메소드에 data를 넘기고, 실패 시 err를 넘김
  
  return new Promise((resolve, reject) => {
    readFile(filePath, "utf8", function (err, text) {
      if (err) {
        reject(err);
      } else {
        resolve(text);
      }
    });
  });
};

getDataFromFilePromise('README.md').then(data => console.log(data));  //err도 출력?

module.exports = {
  getDataFromFilePromise
};

 

3. basicChaining 으로 구현

 

미리 알아야 할 것

[__dirname, __filenaem]
노드에서는 모듈 관계가 있는 경우가 많아 현재 파일의 경로나 파일명을 알아야 하는 경우가 많다. 노드는 __filename, __dirname 이라는 키워드로 경로에 대한 정보롤 제공한다. 파일에 __filename, __dirname 변수를 넣어두면 실행시 현재 파일명과 파일 경로로 바뀐다.
app.set('views',"C:\\Users\\wachsenhaus\\Documents\\guess-MINE\\src\\views") 
app.set('views',`${__dirname}\\views`);
app.set('views',path.join(__dirname,"views"))​

 

-> 위 3개는 같은 주소를 뜻한다.

 

1. getDataFromFilePromise를 이용해, 'files/user1.json' 파일과 'files/user2.json' 파일을 불러온다.
2. 두 파일을 합쳐서 최종적으로 객체가 담긴 배열을 만든다.
const path = require('path');
const { getDataFromFilePromise } = require('./02_promiseConstructor');

const user1Path = path.join(__dirname, 'files/user1.json'); // Users/Doyu/Desktop/.. 
const user2Path = path.join(__dirname, 'files/user2.json'); // 파일 경로가 처음부터 쭉 나온다

const readAllUsersChaining = () => {
  
  return getDataFromFilePromise(user1Path)
      // 여기서 data 는 get 함수의 호출결과인 user1Path의 내용을 담은 data
    .then(user1 => {
      return getDataFromFilePromise(user2Path).then(user2 => {
        return '[' + user1 + ',' + user2 + ']';
      });
    })
    // 파일 읽기의 결과가 문자열이므로, JSON.parse 를 사용해야 한다.
    .then(text => JSON.parse(text))   
}

readAllUsersChaining();

module.exports = {
  readAllUsersChaining
}
// readAllUsersChaining는 이렇게도 쓸 수 있다.

const readAllUsersChaining = () => {
  
  return getDataFromFilePromise(user1Path)
    .then(user1 => {
      return getDataFromFilePromise(user2Path).then(user2 => {
        return [user1, user2].map(val => JSON.parse(val)) 
      });
    })
}

결과과 잘 출력이 되는 모습

 

4. promiseAll

 

Promise.all 은 동시에 두 개 이상의 Promise 요청을 한꺼번에 실행하는 함수이다.
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
  .then((value) => console.log(value))
  // ['1초', '2초', '3초']
  .catch((err) => console.log(err));​

 

const path = require('path');
const { getDataFromFilePromise } = require('./02_promiseConstructor');

const user1Path = path.join(__dirname, 'files/user1.json');
const user2Path = path.join(__dirname, 'files/user2.json');

const readAllUsers = () => {
  return Promise.all([
    getDataFromFilePromise(user1Path),
    getDataFromFilePromise(user2Path)             // 배열 형태로 들어가고
  ])
    .then(([user1, user2]) => {                   // 배열 형태로 나온다.
      return '[' + user1 + ',' + user2 + ']';
    })
    .then(text => JSON.parse(text))
}

readAllUsers()

module.exports = {
  readAllUsers
}
// readAllUsers는 이렇게도 가능하다.

const readAllUsers = () => {
  const user1 = getDataFromFilePromise(user1Path); //user1Path를 받아와서 user1에 할당시켜준다.
  const user2= getDataFromFilePromise(user2Path);
  return Promise.all([user1, user2]).then((value) => { 
    console.log(value);//Promise.all을 사용해서 user1Path, user2Path를 한 배열에 담아준다.
    return value.map((el) => JSON.parse(el));
  })
}

value는 배열 속 문자열의 형태이다. JSON.parse를 해서 객체형태로 만들어 줘야 한다.

 

5. async & await

const { join } = require('path');       
const { getDataFromFilePromise } = require('./02_promiseConstructor');

const user1Path = join(__dirname, 'files/user1.json');
const user2Path = join(__dirname, 'files/user2.json');  // join 작동이 안 되는데?

const readAllUsersAsyncAwait = async () => {
  const json = await Promise.all([                      // **
    getDataFromFilePromise(user1Path),
    getDataFromFilePromise(user2Path)
  ])
  .then(texts => texts.map(text => JSON.parse(text)))
  return json;                                          // **
}

readAllUsersAsyncAwait();

module.exports = {
  readAllUsersAsyncAwait
}
// 이렇게도 쓸 수 있다.

  const readAllUsersAsyncAwait = async() => { 
  const user1 = await getDataFromFilePromise(user1Path);
  const user2 = await getDataFromFilePromise(user2Path);
  return [JSON.parse(user1), JSON.parse(user2)]
}

 

 


깨달음

promise 의 값은 내부에서 확인할 수 있다.

 

문제 상황

  • promise의 return 값을 알고 싶어서 console에 출력하고자 함.
  • 결과는 아래와 같이 Promise {<pending>}이 뜸

 

해결에 참고한 자료

 

The promise will always log pending as long as its results are not resolved yet. You must call .then on the promise to capture the results regardless of the promise state (resolved or still pending):

let AuthUser = function(data) {
  return google.login(data.username, data.password).then(token => { return token } )
}

let userToken = AuthUser(data)
console.log(userToken) // Promise { <pending> }

userToken.then(function(result) {
   console.log(result) // "Some User token"
})​

Why is that? Promises are forward direction only; You can only resolve them once. The resolved value of a Promise is passed to its .then or .catch methods.

 

hence you still get a pending promise because you are reading the Promise too early (i.e. you are writing synchronous code for an asynchronous situation).
-> 결과값을 얻기 위해서도 비동기적으로 코드를 짜야 한다!!!! (전역에서의 콘솔 출력은 동기적 방식)
async function run() {
const data = await asyncFunc();
// Your logic goes here.
}​
You need a .then chained to the function call to make things right (you are then writing correct asynchronous code).

 

해결

** 그냥 위의 코드를 가지고 이리저리 뜯어보다 겪게된 것으로 굳이 아래와 같이 jsonJSON 변수를 새로 선언 및 할당할 필요는 없다. 

const readAllUsersAsyncAwait = async () => {
  const json = await Promise.all([                       
    getDataFromFilePromise(user1Path),
    getDataFromFilePromise(user2Path)
  ])
  const jsonJSON = json.map(each => JSON.parse(each));                               
  console.log(jsonJSON);                               // 값이 의도대로 출력되었다.
  return jsonJSON                           
}
잘 나왔다.

 
 
 

끙끙 붙잡고 있었던 개념들을 주말에 천천히 개념들을 복기해보며 돌아보니 이해가 술술 된다.

역시 시간이 답이다 :)

 

728x90
⬆︎