새소식

프론트엔드 공부/비동기

Async & Promise, fs 모듈 과제

  • -

Bare Minimum Requirements

const sleep = (wait) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('hello');
    }, wait);
  });
};

function runPromise() {
  resetTitle();
  playVideo();

  sleep(1000)
    .then((param) => {
      console.log(param);
      pauseVideo();
      displayTitle();
      return "world";
    })
    .then((param) => {
      console.log(param);
      return sleep(5000);
    })
    .then(highlightTitle)
    .then(sleep.bind(null, 2000))
    .then(resetTitle);
}
  • Promise 실행 함수가 가지고 있는 두 개의 파라미터 resolve 와 reject 는 각각 무엇을 의미하나요?
    • resolve는 성공적으로 이행되었음을 나타내는 데 사용되며 값을 인수로 전달할 수 있습니다. 그런 다음 이 값은 .then에 연결된 모든 콜백에 전달됩니다.  reject는 promise가 실패했음을 나타내는 데 사용되며 값을 인수로 전달할 수도 있습니다. 그런 다음 이 값은 .catch에 연결된 모든 콜백에 전달됩니다.
  • resolve, reject함수에는 전달인자를 넘길 수 있습니다. 이때 넘기는 전달인자는 어떻게 사용할 수 있나요?
    • resolve 및 reject 함수에서 전달된 인수는 각각 결과 또는 에러를 체인의 다음 promise로 전달하는 데 사용됩니다.
    • resolve 또는 reject 함수에 전달된 인수는 pormise에 연결된 .then 또는 .catch 콜백에서 사용할 수 있습니다.
    • 제공한 예제에서 sleep 은 해결되면 문자열 "hello"를 첫 번째 .then으로 전달한 다음 다시 호출한다는 pormise를 반환합니다.
    • 그런 다음 이 콜백은 전달된 인수(문자열 "hello")를 콘솔에 기록하고 비디오를 일시 중지하고 제목을 표시하는 데 사용합니다.
    • 두 번째 콜백은 첫 번째 콜백인 "월드"의 반환 값을 가져와서 기록합니다.
    • 다음 콜백은 pormise인 두 번째 콜백의 반환 값을 가져와서 제목을 강조 표시하는 데 사용합니다.
    • 마지막 콜백은 마지막 pormise의 반환 값을 가져와서 제목을 재설정하는 데 사용합니다.
    • 이런 식으로 전달된 인수를 사용하여 약속 체인을 통해 데이터/결과/오류를 전달하고 이를 기반으로 결정을 내릴 수 있습니다.

const sleep = (wait) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('에러'));
    }, wait);
  });
};


function runPromise() {
  resetTitle();
  playVideo();

  sleep(1000)
    .then((param) => {
      console.log(param);
      pauseVideo();
      displayTitle();
      return "world";
    })
    .then((param) => {
      console.log(param);
      return sleep(5000);
    })
    .then(highlightTitle)
    .then(sleep.bind(null, 2000))
    .then(resetTitle)
    .catch(err => {
    console.log(err);
    })
}
  • new Promise()를 통해 생성한 Promise 인스턴스에는 어떤 메서드가 존재하나요? 각각은 어떤 용도인가요?
    • Promise 인스턴스 메서드 : 통과하거나 거부되었을때의 then , catch 의 내용들 정리
    • 대기(pending): 이행하거나 거부되지 않은 pormise() 호출시 초기 상태.
    • 이행(fulfilled): 연산이 통과됨. 즉, 비동기 처리가 성공적으로 완료되어 이행한 상태. then()
    • 거부(rejected): 연산이 실패함. 비동기 요청 중 에러가 발생했을 때
  • Promise.prototype.then 메서드는 무엇을 리턴하나요?
    • Promise.prototype.then 메서드는 새로운 Promise 객체를 리턴합니다. 이를 통해 체이닝 구조를 구성할 수 있습니다.
    • 이전 연산이 통과되면 해당 반환 값을 콜백받아 사용합니다.
  • Promise.prototype.catch 메서드는 무엇을 리턴하나요?
    • Promise.prototype.catch 메서드 역시 새로운 Promise 객체를 리턴합니다. 이 메서드는 비동기 작업에서 예외가 발생했을 경우 해당 예외를 처리할 수 있는 기능을 제공합니다. 이를 통해 체이닝 구조를 구성할 수 있습니다.
    • 프로미스(promise)에  reject에 대한 콜백을 추가하고 호출된 경우 콜백의 반환값 또는 promise가 이행된 경우 거부된 Promise 객체를 반환합니다.

//sleep 함수를 아래와 같이 바꿉니다.
const sleep = (wait) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("hello");
    }, wait);
  });
};

//브라우저 개발자 도구의 콘솔을 열어 다음을 실행해 본 후, returnValue에 담긴 값을 확인해 보세요.
let returnValue = await sleep(1000);
  • Promise의 세 가지 상태는 각각 무엇이며, 어떤 의미를 가지나요?
    • 대기(pending): 이행하거나 거부되지 않은 pormise() 호출시 초기 상태.
    • 이행(fulfilled): 연산이 통과됨. 즉, 비동기 처리가 성공적으로 완료되어 이행한 상태. then()
    • 거부(rejected): 연산이 실패함. 비동기 요청 중 에러가 발생했을 때
  • await 키워드 다음에 등장하는 함수 실행은 어떤 타입을 리턴할 경우에만 의미가 있나요?
    • await연산자는 Promise를 기다리기 위해 사용됩니다. 연산자는 async function 내부에서만 사용할 수 있습니다.
    • await 문은 Promise가 이행되거나 거부 될 때까지 async 함수의 실행을 일시 정지하고, Promise가 이행되면 async 함수를 일시 정지한 부분부터 실행합니다. 이때 await 문의 반환값은 Promise 에서 이행된 값이 됩니다.
    • await 키워드는 비동기 작업을 순차적으로 처리할 수 있도록 해주는 기능입니다. 그러므로 await 키워드 다음에 등장하는 함수 실행은 Promise 타입을 리턴하는 함수만 의미가 있습니다.
    • 예를 들어, fetch 함수는 Promise 타입을 리턴하므로 await fetch('url')와 같이 사용할 수 있습니다. 하지만 그냥 일반 함수를 실행할 경우에는 에러가 발생할 것입니다.(await 연산자 다음에 나오는 문의 값이 Promise가 아니면 해당 값을 resolved Promise로 변환시킵니다.)
  • await 키워드를 사용할 경우, 어떤 값이 리턴되나요?
    • await 키워드는 해당 키워드 다음에 등장하는 함수가 리턴하는 값을 반환합니다.
    • 예를 들어, fetch 함수는 요청을 보낸 서버로부터 응답을 받는 Promise를 리턴하는데, await fetch('url')는 fetch 함수가 리턴하는 Promise가 resolve 되어 받은 응답을 반환합니다.
    • 만약 await 키워드 다음에 등장하는 함수가 reject 되어 에러를 발생시키면, 그 에러는 현재 함수에서 try...catch를 사용하여 처리할 수 있습니다.

part-2/01_callBack.js 파일을 구현하고, 테스트를 통과합니다

const fs = require("fs");
const { resolve } = require("path");
const { reject } = require("underscore");
const _ = require('underscore');
//콜백이란 다른 코드가 특정코드가 마무리되기 전에 실행되지 않도록, 즉 비동기처리를 위한 방법
const getDataFromFile = function (filePath, callback) {
  // TODO: fs.readFile을 이용해 작성합니다
  // fs모듈을 이용해 콜백 구현
  // getDataFromFile의 filePath, callback의 인자 만들기
  // 조회 성공시 콜백 에러는 없고, 데이터 콜백
  // (err, data)여기서 data는 파일의 내용 
  fs.readFile(filePath, 'utf8', (err, data) => {
    // 콜백 <- 데이터 값이 주어지지 않은 경우 에러, 아니면 데이터
    if (data === undefined) {
      callback(err, null); //(err,data)-> data 는 null, err 첫 인자로 콜백
    } else {
      callback(null, data);//(err,data)-> err 는 null, data 두번째 인자로 콜백
    }
  });

};


// getDataFromFile('README.md', (err, data) => console.log(data));
getDataFromFile("README.md", (err, data) => {
  console.log(data)
});


module.exports = {
  getDataFromFile
};

위의 코드는 "getDataFromFile"라는 이름의 함수를 정의합니다. 이 함수는 "filePath"와 "callback" 두 가지 인자를 받습니다.
함수는 "fs" 모듈을 사용하여 주어진 "filePath"에서 "readFile" 메소드를 사용하여 파일의 내용을 읽습니다.
"utf8"라는 인자는 파일을 읽을 때 utf-8 인코딩 형식으로 읽어들인다는 것을 의미합니다.

읽어들인 파일의 내용을 콜백함수를 통해 받아서 처리하는데, 이때 파일이 없거나 읽는데 문제가 생긴 경우 err에 관련 내용을 담아서 콜백함수를 호출하고, 파일을 읽는데 성공한 경우 data를 파라미터로 하여 콜백 함수를 호출한다.


part-2/02_promiseConstructor.js
callback 이라는 매개변수(parameter) 대신, Promise의 resolve, reject 함수를 이용

Node.js의 fs 모듈은 파일 시스템에 접근하기 위한 API를 제공합니다. 이 모듈을 이용해 파일을 읽거나 쓰는 등의 작업을 할 수 있습니다.

fs 모듈의 메소드들은 대부분 비동기적으로 작동하며, 콜백 함수를 인자로 받습니다. 그러나 이를 Promise로 구현할 수도 있습니다. 이를 위해서는 fs 모듈의 메소드를 감싸는 함수를 생성해 Promise 객체를 반환하면 됩니다.

const fs = require("fs");
// fs 모듈의 메소드를 감싸는 함수(getDataFromFilePromise)를 생성해 Promise 객체를 반환
// getDataFromFilePromise 함수 첫번째 인자로 파일 경로 filePath
const getDataFromFilePromise = filePath => {
  // return new Promise() 
  // TODO: Promise 및 fs.readFile을 이용해 작성합니다.
  // callback 이라는 매개변수(parameter) 대신, Promise의 resolve, reject 함수를 이용
  // 콜백 함수는 인자로 err와 data를 받는다
  // err 가 있다면 reject 함수를 호출하여 에러를 반환하고, 
  // err 가 없다면 resolve 함수를 호출하여 data를 반환
  return new Promise((resolve, reject) => { // 리턴 new Promise 생성
      fs.readFile(filePath, 'utf8', (err, data) => { //콜백자리 인자로 err, data
        if (err) {
          reject(err); //만약 err라면 reject호출 err 반환
        } else {
          resolve(data);//아니면 resolve호출 data 반환
        }
      });
    });
};
// getDataFromFilePromise 함수를 호출시 Promise 객체를 반환. 
// .then()과 .catch()를 호출시 성공적으로 읽어졌을 때와 에러가 발생했을 때에 대한 처리를 할 수 있음. 
getDataFromFilePromise('README.md').then(data => console.log(data));

module.exports = {
  getDataFromFilePromise
};

위 예제에서 readFilePromise라는 함수는 fs.readFile메서드를 감싸고 있으며, 이를 호출하면 Promise 객체를 반환합니다. 이 객체를 이용해 .then()과 .catch()를 호출하여 성공적으로 읽어졌을 때와 에러가 발생했을 때에 대한 처리를 합니다.

readFilePromise 함수는 첫번째 인자로 파일 경로를 받습니다. 그리고 new Promise를 생성합니다. 그리고 fs.readFile 메소드를 호출하면서, 파일을 읽는데 필요한 인자들을 전달합니다. 그리고 콜백 함수를 넘겨줍니다. 콜백 함수는 인자로 error와 data를 받는데, error 가 있다면 reject 함수를 호출하여 에러를 반환하고, error 가 없다면 resolve 함수를 호출하여 data를 반환합니다.

그리고 readFilePromise 함수를 호출하면 Promise 객체를 반환하게 됩니다. 이때 .then()과 .catch()를 호출하여 성공적으로 읽어졌을 때와 에러가 발생했을 때에 대한 처리를 할 수 있습니다. 이렇게 fs 모듈을 이용해 Promise를 구현하면 비동기 작업을 훨씬 편하게 처리 할 수 있습니다.


part-2/03_basicChaining.js은, 앞서 작성한 getDataFromFilePromise를 이용해서 풀어야 합니다.

  • fs 모듈을 직접 사용하는 것이 아닙니다.
  • getDataFromFilePromise를 이용해, 'files/user1.json' 파일과 'files/user2.json' 파일을 불러오고, 두 파일을 합쳐서 최종적으로 두 객체가 담긴 배열을 만드는 것이 목표입니다.
    • 파일 경로를 찾을 때, user1Path 및 user2Path를 이용하세요.
  • then이 어떤 매개변수를 전달받는지에 대한 이해가 있어야 합니다.
  • then의 리턴이 무엇을 의미하는지 이해하고 있어야 합니다.
  • 파일 읽기의 결과가 문자열이므로, JSON.parse 를 사용해야 문제를 해결할 수 있습니다.
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');

// HINT: getDataFromFilePromise(user1Path) 및 getDataFromFilePromise(user2Path)를 이용해 작성합니다
const readAllUsersChaining = () => {
  // TODO: 여러개의 Promise를 then으로 연결하여 작성합니다
  // 객체가 담긴 배열을 만드는 것 -> 빈객체 만들어두고 시작
  let result = [];
  return getDataFromFilePromise(user1Path) // 사용자 가져오기, 진행되면 계쏙진행 배열에 내용 추가
    .then((user1Content) => {
      result.push(JSON.parse(user1Content));

      return getDataFromFilePromise(user2Path);
    })
    .then((user2Content) => {
      result.push(JSON.parse(user2Content));

      return result;
    });

}

// readAllUsersChaining();

module.exports = {
  readAllUsersChaining
}

"path" 모듈과 "getDataFromFilePromise"라는 함수를 가져오는 것으로 시작합니다. 그리고 "user1Path"와 "user2Path"라는 변수를 정의하며, 이들은 파일 시스템에서 특정 파일의 경로를 참조합니다. "readAllUsersChaining"이라는 함수를 정의하며, 이 함수는 파일 시스템에서 사용자1, 사용자2 정보를 읽어오는 프로미스 함수 "getDataFromFilePromise(user1Path)"와 "getDataFromFilePromise(user2Path)"를 연속적으로 실행하며, 그 결과를 객체로 가지는 배열 형태로 만들어서 반환합니다. 마지막에는 해당 함수를 모듈로 내보내는 구문이 있습니다.


part-2/04_promiseAll.js 는 마찬가지로 readAllUsersChaining과 정확히 같은 결과를 리턴합니다. 다만, Promise.all을 반드시 사용해서 풀어야 합니다.

  • Promise.all 은 동시에 두 개 이상의 Promise 요청을 한꺼번에 실행하는 함수입니다.
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');


  // TODO: Promise.all을 이용해 작성합니다
const readAllUsers = () => {
  const user1Promise = getDataFromFilePromise(user1Path);
  const user2Promise = getDataFromFilePromise(user2Path);

  return Promise.all([user1Promise, user2Promise]).then((value) =>
    value.map((user) => JSON.parse(user))
  );
};
// readAllUsers()

module.exports = {
  readAllUsers
}

위의 코드는 "path" 모듈과 "getDataFromFilePromise"라는 함수를 가져오는 것으로 시작합니다.
그리고 "user1Path"와 "user2Path"라는 변수를 정의하며, 이들은 파일 시스템에서 특정 파일의 경로를 참조합니다.
"readAllUsers"라는 함수를 정의하며, 이 함수는 파일 시스템에서 유저1, 유저2 정보를 읽어오는 promise 함수 "getDataFromFilePromise(user1Path)"와 "getDataFromFilePromise(user2Path)"를 각각 정의하고, Promise.all() 메서드를 이용하여 변수로 설정한 유저1, 2promise를 동시에 실행하며, 그 결과를 배열 형태로 만들어서 반환합니다.
그리고 반환된 결과를 각각 JSON.parse()를 이용해 파싱합니다. 마지막에는 해당 함수를 모듈로 내보내는 구문이 있습니다.

  • Promise.all 의 전달인자는 어떤 형태인가요?
    • Promise.all() 메서드의 전달인자는 배열 형태여야 합니다. 배열 안에는 pormise 객체를 전달해야 합니다. Promise.all([promise1, promise2, ...]) 의 형태로 전달됩니다. Promise.all()은 전달받은 배열에 있는 모든 pormise 객체가 fulfilled 상태가 될 때 까지 기다리며, 그 결과를 하나의 배열 형태로 반환합니다. 만약 전달받은 배열에 있는 프로미스 중 하나라도 rejected 상태가 되면, Promise.all() 메서드는 그에 대한 에러를 반환하며, 그 에러는 가장 먼저 rejected 상태가 된 pormise에서 발생한 에러로 반환됩니다. Promise.all()은 비동기 작업을 순차적으로 처리하지 않고, 동시에 실행하여 결과를 모을 수 있게 하는데 사용됩니다. 이를 통해 서로 다른 작업을 동시에 실행하고 그 결과를 모을 수 있어 성능을 향상시킬 수 있습니다.
  • Promise.all 을 사용할 경우에 then 메서드의 매개변수는 어떠한 형태인가요?
    • Promise.all() 메서드를 사용하여 프로미스를 실행하면, 결과를 받을 수 있는 .then() 메서드를 사용할 수 있습니다. .then() 메서드의 전달인자는 함수 형태를 가지며, 이 함수는 인자로 모든 promise의 처리 결과를 담은 배열을 받습니다.
    • 예를 들어, Promise.all([promise1, promise2, promise3])을 실행하면, 각 promise의 처리 결과를 배열 [result1, result2, result3]로 받을 수 있다면, 아래와 같은 형식으로 then 을 사용할 수 있습니다.
Promise.all([promise1, promise2, promise3]).then((results) => {
  console.log(results); // [result1, result2, result3]
});
  • Promise.all 에 두 개의 Promise 요청이 전달되고, 만일 그중 하나가 rejected 상태가 되는 경우, then 메서드, catch 메서드 중 어떤 메서드를 따라갈까요?
    • Promise.all() 메서드를 사용할 경우, 전달된 promise 중 하나라도 rejected 상태가 되면, 그 promise는 catch() 메서드를 통해 처리됩니다. 이러한 경우 then() 메서드는 실행되지 않습니다.
    • 예를 들어, Promise.all([promise1, promise2]) 중 promise2가 rejected 상태가 되는 경우, then() 메서드를 통해 처리되지 않고, catch() 메서드를 통해 promise2의 에러를 처리할 수 있습니다.
    • 또한, 전달된 promise 중 하나라도 rejected 상태가 되면 Promise.all() 메서드 자체도 rejected 상태가 되며 catch 메서드를 통해 에러를 처리할 수 있습니다.
Promise.all([promise1, promise2])
  .then((results) => {
    console.log("This will not be executed");
  })
  .catch((error) => {
    console.log(error);
  });

part-2/05_asyncAwait.js은, 앞서 진행한 readAllUsersChaining, readAllUsers와 같은 결과를 리턴합니다. 이번에는 async 및 await 키워드를 사용해서 해결해야 합니다.

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 readAllUsersAsyncAwait = async () => {
  const user1 = await getDataFromFilePromise(user1Path);
  const user2 = await getDataFromFilePromise(user2Path);

  return [user1, user2].map((user) => JSON.parse(user));
};
// readAllUsersAsyncAwait();

module.exports = {
  readAllUsersAsyncAwait
}

위의 코드는 "path" 모듈과 "getDataFromFilePromise"라는 함수를 가져오는 것으로 시작합니다. 그리고 "user1Path"와 "user2Path"라는 변수를 정의하며, 이들은 파일 시스템에서 특정 파일의 경로를 참조합니다.
"readAllUsersAsyncAwait"라는 함수를 정의하며, 이 함수는 async/await 구문을 사용하여 파일 시스템에서 유저1, 유저2 정보를 읽어오는 promise 함수 "getDataFromFilePromise(user1Path)"와 "getDataFromFilePromise(user2Path)"를 각각 정의하고, await 키워드를 사용하여 함수를 순차적으로 실행하며, 그 결과를 배열 형태로 만들어서 반환합니다. 반환된 결과를 각각 JSON.parse()를 이용해 파싱합니다. 마지막에는 해당 함수를 모듈로 내보내는 구문이 있습니다.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.