TIL #18 Promise Function => Article Collector

Joshua Song / 송성현·2019년 12월 27일
0

이머시브_16

목록 보기
21/29

Promise (프로미스 함수)

  • 자바스크립트에서 비동기 함수를 처리하고 싶을 때 사용할 수 있는, ES6부터 가능한 비동기 처리 패턴이다. 비동기 처리 시점을 명확히 표현하기도 한다.

  • 콜백 패턴의 단점
    - 비동기식 처리를 위해 콜백 패턴을 사용할 시 처리 순서를 위해 여러 개의 콜백 함수가 네스팅 (중첩)되어 가독성도 나쁘게 하고 복잡도도 높아진다. 에러 처리도 힘들고...Callback Hell이 된다.

    • catch 블락에서 캐치되지 않는다...

프로미스 생성

  • 프로미스 함수는 Promise 생성자 함수로 만들고 resolve와 reject 함수를 인자로 받는다.
const izone = new Promise((resolve, reject) => {
  //비동기 작업
  
  if (/*비동기 작업 성공 */) {
    resolve('성공');
  }
  else { /* 비동기 작업 수행 실패*/
    reject('failure :(');
}

})
                          

Promise 함수는 비동기 처리가 성공인지, 실패했는지에 관해 상태(state)정보를 갖는다. 하도 에러를 많이 봐서...익숙하다 특히 rejected.

pending 비동기 처리가 아직 수행되지 않은 상태: resolve 또는 reject 함수가 아직 호출되지 않은 상태
fulfilled 비동기 처리가 수행된 상태 (성공): resolve 함수가 호출된 상태
rejected 비동기 처리가 수행된 상태 (실패): reject 함수가 호출된 상태
settled 비동기 처리가 수행된 상태 (성공 또는 실패): resolve 또는 reject 함수가 호출된 상태

프로미스 후속 처리

  • Promise 로 구현된 비동기 함수는 Promise객체를 반환하기에 그 후에 then이던 catch를 통해 처리 결과 또는 에러 메시지를 호출한다.
  • then
    - then 메소드는 성공 시 호출 되는 함수와 실패 시 호출되는 함수를 두개 호출할 수 있다. Promise 를 반환한다.
    .then(
      //성공시 호출될 함수
      render,
      //실패시 호출될 함수
      console.error
      );
  • catch
    - 예외가 발생하면 호출돼 처리한다. Promise를 반환한다.
  • 에러를 처리할 때 then 메소드의 두 번째 콜백 함수보다는 catch를 쓰는 게 더욱 효율적인 이유는 then 메소드의 두 번째 콜백함수는 비동기 처리에서 발생한 에러만 캐치하는 반면 catch는 비동기 처리 중 발생하는 에러와 더불어 then 메소드 내부에서 발생한 에러도 캐치한다.

.then을 적절하게 사용해 promise함수를 구현하는 것이 chaining이다.

Promise.all && Promise.race

  • Promise.all을 사용하면 배열안에 있는 Promise 함수들을 순서대로 처리하고 그 처리 결과를 resolve하는 새로운 프로미스를 반환한다. 배열 안에 그 결과 값이 담겨서 반환될 것이다.
  • Promise.race를 사용하면 배열 안에 있는 Promise 함수들 중 가장 먼저 처리되는 프로미스 함수가 resolve한 결과값을 resolve하는 새로운 프로미스를 반환한다.

참고 : Poiema

async && await

  • ES8 부터 정의된 새로운 문법인 async와 await을 사용해 비동기 코드를 작성할 시 더욱 쉽게 작성할 수 있다.
  • 사용법도 간단하다. function 앞에 async를 붙여주고 비동기 처리되는 부분에 await을 붙여준다. 명심해야 할 부분은 둘 다 Promise 함수여야 한다는 점이다.
async function goWork(time1, timeStartWork) {
  const time2 = await wakeUp(time1)
  const time3 = await takeSubway(time2)
  const time4 = await takeOffSubway(time3)
  const arrivalTime = await arriveWork(time4)
  if (arrivalTime > timeStartWork) {
    fire()
  }
}

//출처: https://blueshw.github.io/2018/02/27/async-await/

Promisify

  • Promisify는 비동기로 처리되는 함수를 node.js 내장함수인 util모듈에 있는 함수를 사용해 Promise함수로 변환하는 것이다. 이것을 쓰면 바로 일반 콜백 함수가 Promise화 되어 바로 쓸 수 있다.
  • fs의 method들 중에는 비동기가 많기 때문에 Promisify를 사용하면 편하다.

Sprint - Article Collector

  • 이번 스프린트를 진행하기전 Promise함수를 연습하는 스프린트도 있었지만 그건 이번 스프린트를 위한 초석이었기 때문에 넘어간다.
  • Article Collector는 URL을 입력했을 때 서버가 각각의 URL을 https로 요청한 후, 응답을 받아와 저장한 후에 출력하는 프로그램이다. 서버 백 엔드에서 fs 를 사용해 받아온 정보를 저장하고 읽어준다.

Objectives

  • export 와 require statements를 이용해 well-organized codebase를 만든다.
  • fs module을 활용해 파일을 다룰 줄 안다.
  • https 또는 http module을 활용해 서버 상에서 HTTP 요청하는 법을 익힌다.
  • HTTP server routing 을 다루면서 웹 서버가 어떻게 작동하는지 이해한다.
  • Node Application을 디버깅 할 수 있다.

흐름 파악

  • 이번 스프린트는 전체적인 흐름을 파악한 후 내가 실제로 짜야할 코드는 helper function 의 fetch 와 file 파일, 그리고 routes의 source 파일이다.
  • /helpers/fetch.js :
    - retrieveArticle: url 이 주어졌을 때 url에 요청을 해 그 안의 html 문자열을 가져오는 프로미스 함수를 리턴한다.
  • /helpers/fetch.js:
    - writeFile: 파일에 내용을 저장한다.
    • readFile: 파일에 있는 내용을 가져와 읽는다.
    • readLineFromSourceList: 파일에 있는 내용 중 파라미터로 들어오는 숫자 번째의 내용을 읽어 반환한다.
  • routes/source.js:
    - get: 할퍼에 있는 함수를 이용해 파일을 읽고, 그 내용을 전송해준다.
    • post: 할퍼에 있는 함수를 이용해 들어오는 내용을 파일에 저장해준다.

retrieveArticle

const request = require('request')

async function retrieveArticle(url) {
  return new Promise((resolve, reject) => {
    const options = {
      url: url,
      encoding: null
    };
    request(options, function (error, response, body){
      if (error){
        reject(error)
      }
      if (response){
        console.log(body)
        resolve(body)
      }
    })
    
  });
}

// TODO: retrieve the html string from given url and return as promise
module.exports = {
  retrieveArticle
};
  • 위 코드를 보면 request모듈을 사용하는 것을 볼 수 있는데 사실 https 모듈을 사용해서 가져왔었다. 하지만 https와 http url을 둘 다 적용해보고 싶었고 그 결과 request모듈을 사용했다.
  • options에 필요한 정보를 넣어 그 정보를 토대로 요청을 넣어 받아오는 프로미스 함수를 만든다.
  • 상당히 straightforward하다.

file.js

async function writeFile(filename, body) {
  return new Promise((resolve, reject) => {
    fs.writeFile(filename, body, (err) => {
      if (err) {
        reject(err);
      } else {
        resolve(body);
      }
    });
  });
    // TODO: save the content with specific filename
}

async function readFile(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) {
        reject(err)
      }
      else {
        resolve(data)
      }
    })
  });
    // TODO: read the content with specific filename
  
}
  • writeFile 과 readFile 모두 straightforward하다. 한가지 의문은 async 로 실행하기 때문에 await을 사용하면 될 것 같지만 이상하게도 에러가 떠서 그냥 new Promise로 생성한 기억이 있다.
  • fs 모듈을 사용해 적합하게 저장과 읽기를 한다.
async function readLineFromSourceList(nthline) {
  return new Promise((resolve, reject) => {
    fs.readFile("./data/source.txt", 'utf8', (err, data) => {
      if (err){
        reject(err)
      }
      else {
        resolve(data.split("\n")[nthline])
      }
    })
    // TODO : read line via specific nthline
  });
}
  • readLineFromSourceList는 fs.readFile로 파일을 읽어와 배열로 변환 시킨 후 (빈칸 없이) 파라미터로 들어오는 nthline 번째 스트링을 리졸브 해준다.
  • 이렇게 보면 쉬운데 할때는 헷갈렸다.

source.js

router.get('/', async (req, res) => {
  
      fileHelper.readSourceListFile()
      .then(data => res.status(200).send(data))  
  // TODO: read the content from source.txt using fileHelper
});
// POST /source
router.post('/', async (req, res) => {
  fileHelper.writeSourceListFile(req.body)
  .then(res.status(200).send('ok'))

  // TODO: save the content to source.txt using fileHelper
});
  • get 인 method로 요청이 들어올 때는 helper 에서 작성한 readSourceListFile을 실행해주고 post일 때는 들어오는 정보로 writeSourceListFile을 실행해준다. Simple!

Advanced

  • 생각보다 빠르게 구현할 수 있어 advanced를 도전해 보았다. 일단 request 모듈을 사용해 http 형태의 url도 받았고 또 medium blog가 아닌 다른 형태의 HTML도, 웹사이트의 HTML 이 아닌 그냥 body를 가져옴으로 해결했다.
  • 텍스트가 아닌 binary 형태의 파일(이미지)를 불러오는 부분은 react-html-parser 라는 모듈을 사용해 해결 했다. 이 모듈을 string안의 태그를 보고 그거에 맞게 JSX로 변환해준다. 실제로 그 전에는 이런 식으로 안의 내용까지 스트링으로 다 변환해 불러왔다면 이제는 태그를 보고 이미지는 이미지로 변환해서 보여준다.

OUTCOME

Conclusion

  • 돌이켜보며 리뷰했을 때 그렇게 어려운 거 같진 않은데 promise함수를 사용할 수 있는 좋은 기회였다.
  • react-html-parser라는 모듈을 사용할 수 있어 좋았고 시간만 더 있었다면 stream에 관해서도 더 이론적으로 자세히 알아보고 싶다.
  • 블로깅을 1주일 지나 하고 있는데 참 시간이 많이 걸린다. 서버를 하도 많이 해서 그런가 전보다 좀 더 익숙해진 기분이 든다.
  • 계속 화이팅 하자~!
profile
Grow Joshua, Grow!

0개의 댓글