Curl Up Black Cat


이번 포스트에서는 DOCTYPE html 선언 의미가 무엇인지에 대해서 알아봅니다.

<!DOCTYPE html>

HTML 파일내 DOCTYPE 을 선언하게 되면 이 문서는 웹 표준 문서이자 모든 웹브라우저에서 잘 돌아가는 호환이 되는 문서가 됩니다.
반대로 DOCTYPE 을 선언하지 않으면 비표준 문서가 됩니다.
표준과 비표준의 차이는 브라우저 화면을 그리는 방법을 스위치 한다고 할 수 있습니다.

모든 html 문서의 시작은 해당 문서가 어떤 문서 형식을 따르고 있는지 선언하는 것 부터 시작되며, 대소문자 구별없이 단지 선언을 하는 것이 중요합니다.
HTML 태그 역시 대문자와 소문자를 구별하지 않고 사용할 수는 있지만 가급적 소문자 사용을 권고합니다.

DOCTYPE 선언을 하지 않는 경우와 선언하는 경우를 파이어폭스 브라우저에서 확인해 보면..

DOCTYPE 비선언


DOCTYPE 선언


브라우저의 렌더링 방식이 DOCTYPE 을 선언하지 않으면 쿼크모드(Quirks mode) 이고 선언하면 표준 호환 모드(Standard mode) 로 차이가 있는것을 확인할 수 있습니다. 이에 따라 컨텐츠를 렌더링 하는 위치도 조금 다른것을 확인할 수 있습니다.

브라우저는 출력하고자 하는 문서가 최신이라고 판단하면 표준모드로 렌더링을 하고, 반면 예전 문서라고 판단하면 쿼크모드로 렌더링을 하게 됩니다. 쿼크 모드의 목적은 오래된 웹페이지들이 최신 버전의 브라우저에서 단지 깨져 보이지 않으려는 것에 있고 비표준적 방법의 CSS를 적용합니다.

중요한건.. 웹 표준으로 작성하는 문서가 오늘날 존재하는 대부분의 브라우저에서 올바르게 동작하기 때문에.. HTML은 DOCTYPE 을 선언하고 표준으로 문서를 작성해야 합니다.

'💻 프로그래밍 > 🅷 HTML' 카테고리의 다른 글

시멘틱 마크업(Semantic Markup) 이란?  (0) 2022.11.12
HTML 이란?  (0) 2022.11.12
HTML 무료 템플릿  (0) 2021.07.17

이미지 출처: medium.com/codex

 

시멘틱 마크업(Semantic Markup) 이란?

HTML (HyperText Markup Language)은 웹사이트 컨텐츠를 설명하는데 사용되는 마크업 언어입니다.

 

HTML 은 컨텐츠의 의미를 설명하는데 유일한 목적을 가지고 있습니다.

비주얼 디자인(모양, 색, 크기 등) 이 목표가 아니라, 구조 설계(Structure Design)를 목표로 합니다.

 

HTML 파일에 컨텐츠를 작성하고 파일을 저장하여 웹브라우저로 실행해 보면..

 

 

웹 브라우저는 컨텐츠를 해석 해서 구조를 설계하게 됩니다. 구조를 설계한 부분을 확인 해볼려면 개발자 도구를 통해서 확인할 수 있습니다.

 

 

Elements 패널을 확인해 보면 이전에 작성하지 않은 <html>, <head>, <body> 라는 태그가 생성되어 있고 <body> 태그 안에 컨텐츠로 작성한 내용이 들어가 있습니다. 브라우저는 컨텐츠를 해석하게 되면서 자동으로 만들지 않았던 태그들을 구성하게 됩니다. 이러한 일을 마크업(Markup) 이라고 합니다.

 

위 경우는 내용만 있고 의미를 부여하지 않았기 때문에 논 시멘틱 마크업(non-semantic markup) 이라고 할 수 있습니다. 한마디로 의미가 없는 형태의 구조입니다.

 

여기에 간단히 구조를 작성해 보겠습니다. 제목(heading)을 의미하는 h 태그와 단락(paragraph)을 의미하는 p 태그를 사용하여 시작과 끝을 감싸주고 다시 브라우저로 실행하게 되면..

 

 

앞서 작성했던 문서와 다르게 브라우저는 하나의 컨텐츠로 인식하게 되며 제목 요소와 단락 요소를 구분할 수 있게 됩니다.

이를 통해서 브라우저는 제작자가 어떤 목적을 가지고 요소에 의미를 부여해서 보여주는지를 비로소 이해하게 됩니다.

 

이처럼 시멘틱 마크업은 매우 중요한 작업입니다.

컨텐츠에 의미를 부여하는 구조적 설계시멘틱 마크업(Semantic Markup)이라고 합니다.

 

시멘틱 마크업(Semantic Markup)을 왜 해야 할까?

월드 와이드 웹은 단순히 보여지는 것만이 전부가 아닙니다.

예를 들어 검색 로봇 또는 브라우저와 같은 기계, 볼 수 없는 사람(시각 장애자)은 보이는 것이 아닌 정보로 데이터에 접근하고 수집합니다.

쉽게 생각해서 드라마를 오디오만으로 듣는다고 생각해보면..

 

감정을 나타내기 위해서는 억양이 거세거나, 부드럽거나, 크게 외치거나, 작게 소근거려야 청취자들에게 전달이 되겠죠?

모두 동일한 음으로 이야기 한다면 아무런 감정을 나타낼 수 없을 겁니다.

 

마찬가지로 HTML 문서에 사용된 요소를 문맥에 맞게 분석하고, 적절한 요소를 통해 구조화 해야 비로서 정보의 체계(System)가 갖추어지게 됩니다.

 

HTML 이 쉽다고 말하는 사람들은 단순히 문법만 놓고 언어를 판단하기 때문에 그리 말하는 것으로, 문서의 구조를 고민하고, 판단하에 적절한 의미를 부여하는 일은 결코 쉬운 일이 아닙니다.

 

'💻 프로그래밍 > 🅷 HTML' 카테고리의 다른 글

DOCTYPE html 선언과 의미  (0) 2022.11.12
HTML 이란?  (0) 2022.11.12
HTML 무료 템플릿  (0) 2021.07.17

이번포스팅은 HTML 이 무엇인지에 대해서 알아봅니다.

HTML 이란 ?

HTML 은 Hyper Text Markup Language 의 약자입니다.
각 단어의 의미를 순서대로 살펴보면..
Hyper 의 뜻은 '뛰어넘다', '초월하다'의 의미를 가지고 있습니다.
Hyper Text 는 사용자 경험에 따랐을때 텍스트에 밑줄이 그어지게 되고 사용자가 이것을 클릭하게 되면 페이지가 전환이 되는 링크 형태의 시스템을 말합니다.
Markup 은 어떤 구조를 설정할때 사용이 되며..
Language 는 언어입니다.

결과적으로 HTML 이란 언어는 구조를 설계할적에 사용되는 언어이며 하이퍼링크(hyperlink) 시스템을 가지고 있습니다.

모든 파일은 확장자라는 포맷을 가지고 있습니다. (jpg, gif, psd, ai, pdf, doc...)
HTML 파일은 htm 또는 html 확장자 포맷을 사용합니다.

HTML 문서는 단순한 텍스트 파일에 불과합니다.
이 텍스트 파일을 웹브라우저가 해석해서 구조를 통해 화면에 사용자가 보도록 렌더링(Rendering)을 해주게 됩니다.
(렌더링이란 그림을 그리는 것입니다.)
사용자는 결국 브라우저를 통해 View 라고 하는 화면을 보게 됩니다.

'💻 프로그래밍 > 🅷 HTML' 카테고리의 다른 글

DOCTYPE html 선언과 의미  (0) 2022.11.12
시멘틱 마크업(Semantic Markup) 이란?  (0) 2022.11.12
HTML 무료 템플릿  (0) 2021.07.17

이미지출처: pixabay

이번에 작성하는 포스트는 Git 되돌리기에 관한 내용입니다

과거에 일어난 일을 되돌릴수 있다면 얼마나 좋을까요?

영화 백투더퓨처는 마틴박사가 발명한 타임머신 이라고 불리는 자동차를 타고 과거의 시간여행을 할 수 있습니다.

이미지출처: 자유아시아방송

아직까지 현실세계에서는 과거로 시간여행은 불가능하지만..
Git 에서는 과거로 돌아갈 수 있고 과거의 일어난 일을 되돌릴 수 있는 방법을 제공합니다.

과거의 일어난 일을 되돌리는 방법이 두가지가 있습니다.
바로 Reset 과 Revert 명령어를 사용하는 것입니다.
Reset 은 시간을 예전으로 되돌리는 것이고 Revert 는 특정 커밋을 없었던 일로 만듭니다.

1. Reset

앞에서 설명한대로 Reset 은 시간을 다시 맞추는 것입니다. 돌아갈려는 커밋 시점으로 레파지토리는 재설정되고 해당 커밋 이후의 이력은 사라집니다. 아래와 같이 사용합니다.

$ git reset <옵션> <돌아가고싶은 커밋 ID>
# 옵션을 적지 않으면 mixed 로 동작합니다.


옵션은 3가지(hard, mixed, soft)가 있습니다.

1) hard: 돌아가려는 이력 이후의 모든 변경 이력을 삭제 합니다.

2) mixed: 이력을 되돌리고 이후에 변경된 내용에 대해서는 남아 있지만 인덱스는 초기화 됩니다. 커밋을 하려면 add 명령어로 stage 에 반영하고 commit 해야 합니다.

3) soft: 돌아가려는 이력으로 되돌아 갔지만, 변경내용은 stage 에 반영되어 있는 상태입니다. 바로 다시 커밋 할 수 있는 상태로 남아 있는 것입니다.

위 명령어를 사용한다고해서 origin 은 변경되어 있지 않습니다.
--force 옵션을 주어 이럴땐 강제로 반영해야 하는데..
다른사람들과 레파지토리르 공유하고 있다면 무조건 하면 안되는 행동입니다.

$ git push -f origin <브랜치명>

 

2. Revert

Rever 는 특정 커밋을 없었던 일로 만드는 것입니다. Reset 과 다르게 커밋을 삭제하는 것이 아닌 커밋을 추가하는 방식입니다.
이전 이력은 그대로 있고, 되돌리려는 커밋만 되돌리는 방식입니다. 이전의 일은 기억하고 있지만 그 내용은 알지 못하는 것처럼 말이죠.
아래와 같이 사용합니다.

$ git revert <되돌릴 커밋>

되돌릴 커밋이 여러개라면 범위를 주어서 여러개를 선택할 수도 있습니다.
아래처럼요.

$ git revert 26634dd2..78652aa2

 

'💻 프로그래밍 > 🅶 Git' 카테고리의 다른 글

이미 push된 파일 .gitignore 적용하기  (0) 2021.08.09


오늘은 실무에서 node 기반의 API 개발을 하면서 고민했었던 응답처리와 이를 해결했었던 알고리즘 구현에 대해서 포스팅 합니다.

어느날 같이 협업하고 있는 프론트엔드 엔지니어분이 응답 데이터중에서 특정 속성 값이 null 인 경우는 빈값으로 치환해 달라고 요청을 받게되었습니다. API 데이터에서 null 값이 내려와 프론트에서 에러가 난다는 내용과 함께.. 특정 속성필드는 DB 값은 null 이겠지만 응답에서는 빈값으로 치환해달라는 요청이였던 거죠.

요청을 받고 당시 좀 고민을 하였습니다. 과연 null 값을 응답하는 것이 부적절 한가? 에 대한 생각이 들었습니다.
그렇게 생각이 들었던 이유는.. 현재 채택한 스택의 데이터베이스는 NoSQL DB 가 아닌 SQL DB인 MySql 이였기 때문입니다.

차이점을 조금 살펴보면..
SQL (관계형DB) 의 데이터는 정해진 데이터 스키마에 따라 테이블에 저장되게 됩니다. 여기서 중요한건 SQL 은 지정된 틀이 있다는 점입니다. 스키마를 준수하지 않은 레코드들은 테이블에 추가할 수도 없습니다.

반면 NoSQL (비관계형 DB)는 말 그대로 SQL(관계형 DB) 와는 반대입니다. 스키마도 없고 관계도 없습니다. NoSQL 에서는 레코드를 문서(documents) 라고 부릅니다. 이것은 단순히 이름만 다른 것이 아니라, 핵심적인 차이점이 있습니다. SQL 세상에서는 정해진 스키마를 따르지 않는다면 데이터를 추가할 수 없지만, NoSQL 에서는 다른 구조의 데이터를 같은 컬렉션(=SQL에서의 테이블)에 추가할 수 있습니다.

필자는 당시 이에따라 API 응답데이터도 차이가 있어야 하지 않을까? 라고 생각이 들었습니다. SQL 에서는 값이 있던 없던 지정된 스키마의 순수데이터가 응답되는게 맞다는 생각이 들었고. 반면 NoSQL 에서는 속성값이 존재하지 않는다면 response 에서 생략해야 한다는 생각을 갖고 있었습니다.

지금 생각해보면 당시에는 REST API 에 대한 개념이 조금 부족했던거 같습니다..🥲

클라이언트에서 서버의 디비형태를 알수 있게 응답 데이터를 제공할 필요가 있었을까?.. 라는 생각이 듭니다. 


어쨋든 당시에 업무의 요청을 수용하고 싶었고 그래서 그동안 응답데이터들중 null 값을 가진 속성이 있다면 빈값으로 치환해 주는 작업을 진행하게 되었습니다. 

 

사실 express 에서는 json null 필드를 response 에서 생략하는 방법을 간단하게 제공합니다.

const app = express()
// JSON null 필드 생략
app.set('json replacer', (k, v) => (v === null ? undefined : v))


이렇게 간단한 방법이 있었는데...
제길... 노드와 express의 생태계를 제대로 숙련하지 못한 초보자인 필자는 방법을 알지 못하여 알고리즘을 짜기 시작하였습니다.

객체의 속성값에 접근하여 치환하는 알고리즘을 구현하여 결국 요청을 수용하였습니다. 🥲 모르면 몸이 고생한다는 말을 이런 상황을 두고 하는 말인거 같네요 

필자는 값을 구하는 utils 클래스의 함수를 구현하여 문제를 해결하였는데요.
구현 했던 코드를 한번 살펴보겠습니다.

const ValueUtils = {}

ValueUtils.empty = (str, includeBlank = true) => {
  const empty = str === null || str === undefined || (includeBlank && str === '')
  return empty
}

ValueUtils.nvl = (str, defaultValue = '') => {
  if (ValueUtils.empty(str)) { return defaultValue }
  return str
}

ValueUtils.clearNull = (obj, defaultValue = '') => {
  if (ValueUtils.empty(obj)) { 
    return ValueUtils.nvl(obj, defaultValue) 
  }
  
  if (Array.isArray(obj)) {
    obj.forEach((el) => ValueUtils.clearNull(el, defaultValue))
    return obj
  }

  Object.keys(obj).forEach((key) => {
    if (Array.isArray(obj[key])) {
      obj[`${key}`] = ValueUtils.clearNull(obj[key], defaultValue)
    } else {
      obj[`${key}`] = ValueUtils.nvl(obj[key], defaultValue)
    }
  })
  return obj
}

module.exports = ValueUtils

clearNull 이란 함수는 전달된 obj 의 속성을 접근하여 속성이 비어있는 값을 치환해 주는 역할을 하는 함수입니다.
- 전달된 속성이 있는지 확인
- 속성이 배열인지 확인 -> 배열인 경우는 요소에 접근하여 재귀호출
- Object 인 경우는 속성값이 배열인 경우는 요소에 접근하여 재귀호출, 배열이 아닌경우를 치환값으로 처리하는 로직입니다.

nvl 함수 오라클에서 사용되는 NULL 처리 함수로서 data 값이 null 값일 때 임의 설정값으로 처리해주는 함수를 구현하였습니다.

위 함수를 사용하게되면 아래와 같이 응답데이터중 null, undefined, 빈값등의 데이터를 원하는 값으로 치환해주는 작업을 손쉽게 할 수 있습니다. 🙂

exports.get = async (documentId) => {
  try {
    const responseModel = await Document.findById(documentId)
    return ValueUtils.clearNull(responseModel)
  } catch (err) {
    throw err
  }
}

 

'💻 프로그래밍 > 🅽 Node.js' 카테고리의 다른 글

Factory Design Pattern 2  (0) 2022.09.23
Factory Design Pattern 1  (0) 2022.09.21
Retry Promise Design Pattern  (0) 2022.09.20
Promise.all 병렬 실행  (0) 2022.09.19
return vs return await 함정  (0) 2022.09.19

출처: pixabay

이번에 다룰 내용은 팩토리 패턴의 캡슐화를 강제할 수 있는 메커니즘에 대해서 내용을 작성하게 되었습니다.
팩토리 패턴 첫번째 내용이 궁금하신분들은 여기를 살펴보세요🙂

팩토리는 클로저 덕분에 캡슐화 메커니즘으로 사용될 수도 있습니다.

캡슐화는 상속, 다형성 및 추상화와 함께 객체지향 디자인의 기본 원칙입니다. 캡슐화는 외부 코드가 컴포넌트의 내부 핵심에 직접 접근하여 조작하는 것을 방지하기 위해 접근을 제어하는 것을 의미합니다. 컴포넌트와의 상호작용은 public 인터페이스를 통해서만 가능하여, 컴포넌트의 상세 구현의 변경으로 부터 외부 코드를 분리시킬 수 있습니다.

JavaScript 에서 캡슐화를 적용하는 주요 방법은 함수에 스코프(Scope)와 클로저(Closure)를 사용하는 것입니다.
팩토리는 쉽게 private 변수들을 강제 할 수 있는데요 코드를 통해 확인해보겠습니다

function createPerson(name) {
  const privateProperties = {}
  
  const person = {
    setName (name) {
      if (!name) {
        throw new Error('A person must have a name')
      }
      privateProperties.name = name
    },
    getName () {
      return privateProperties.name
    }
  }
  
  person.setName(name)
  return person
}

코드에서 팩토리 함수안에 클로저를 사용해서 두 개의 객체를 생성했습니다.
첫번째. 팩토리가 반환하는 퍼블릭 인터페이스인 person 객체
두번째. 외부에서 접근할 수 없고 person 객체가 제공하는 인터페이스만을 통해 접근할 수 있는 privateProperties

마치 class 에서 private 필드를 선언하여 객체를 생성한 방식의 처리를..
팩토리 함수에 클로저를 두어 구현한 코드라고 이해하면 될거 같습니다.
(name 값은 필수값으로 두었기 때문에 생성된 person 객체는 name 속성을 반드시 가지고 있다는 것을 알 수 있습니다.)

이렇게 팩토리에서도 손쉽게 캡슐화 처리를 할 수 있는 메커니즘을 알아 보았습니다.

JS 에서 팩토리에 대한 다양한 방식이 있습니다. 하지만 중요한건 캡슐화를 지킬 수 있다는 매커니즘입니다.
이 방식을 이해했다면 다양하게 응용할 수 있을거 같습니다.

반변 캡슐화의 장점을 모른다면 어떤게 좋은지 이해하지 못한 내용일 수도 있을거 같습니다.
저는 프로그래밍을 하면서 제일 중요하다고 생각하는게 캡슐화라고 생각합니다.
캡슐화에 대한 자세한 내용은 다음번에 한번 정리해보도록 하겠습니다. 🙂

'💻 프로그래밍 > 🅽 Node.js' 카테고리의 다른 글

null 속성값 치환 알고리즘  (0) 2022.10.30
Factory Design Pattern 1  (0) 2022.09.21
Retry Promise Design Pattern  (0) 2022.09.20
Promise.all 병렬 실행  (0) 2022.09.19
return vs return await 함정  (0) 2022.09.19

출처: pixabay

Node.js 에서 가장 일반적인 디자인 패턴은 팩토리(Factory) 패턴이라고 합니다. 사실 저는 팩토리 패턴을 잘 사용하지 않았습니다. 팩토리 패턴에 대해서 알아보고 상황에 따라 잘 사용해보고 싶어 관련 내용을 정리하였습니다.

팩토리 패턴은 매우 다양하며 한가지 이상의 목적을 가지고 있습니다. 주요 장점은 특정 구현으로 부터 객체의 생성을 분리할 수 있고 실행시 생성되는 객체를 결정할 수 있습니다. 그리고 캡슐화 처리에 좀 더 좋습니다.

우선 이번 포스터에서는 객체 생성과 구현의 분리에 관한 내용을 다뤄보겠습니다.

JavaScript 에서 단순성, 유용성 및 작은 노출 면으로 인해 순수한 객체지향 디자인 보다 함수형 방식이 더 선호됩니다. 특히 새로운 객체의 인스턴스를 만들 때 그렇습니다. 실제로 new 연산자 혹은 Object.create() 를 사용하여 클래스로부터 새로운 객체를 만드는 대신에 팩토리 함수를 호출하는 것이 여러 측면에서 훨씬 더 편리하고 유연할 수 있습니다

무엇보다 팩토리 패턴을 사용하게 되면 객체 생성과 구현을 분리할 수 있고 특정 조건에 따라 다른 유형의 객체를 반환하도록 할 수도 있습니다.

Image 객체를 생성하는 간단한 코드를 한번 살펴 보겠습니다.

function createImage(name) {
  return new Image(name)
}
const image = createImage('sample.jpeg')

위 createImage() 함수는 전혀 불필요하게 보입니다. 왜 new 연산자를 사용해 직접 인스턴스를 생성하지 않았을까 라는 생각이 들정도로 그렇습니다. 그냥 아래와 같이 작성해도 되지 않았을까요?

const image = new Image('sample.jpeg')


하지만 new 를 사용하면 코드를 특정 유형의 객체에 바인딩 하게 됩니다.
이미지 형식마다 하나의 클래스를 지원하기 위해 Image 클래스를 더 작은 클래스로 분할한다고 생각해 볼 경우 팩토리 함수로 생성한 경우는 기존 사용된 코드들은 전혀 손댈 필요 없이 다음과 같이 함수에 구현된 코드만 수정하면 됩니다.

function createImage(name) {
  if (name.match(/\.jpe?g$/)) {
    return new ImageJpeg(name)
  } else if (name.match(/\.gif$/)) {
    return new ImageGif(name)
  } else if (name.match(/\.png$/)) {
    return new ImagePng(name)
  } else {
    throw new Error('Unsupported format')
  }
}


위와 같이 관련된 객체 생성을 묶어서 관리할 수 있고 또한 클래스를 숨겨, 멋대로 확장하거나 수정하는 것을 막아줍니다. 이것은 캡슐화의 이점이라고 할 수 있습니다.
JavaScript 에서는 팩토리만 사용하도록 함으로써 클래스를 비공개로 유지할 수 있다고 합니다.

팩토리에 관한 첫번째 포스터는 여기까지 작성하고 다음 포스터에서 내용 이어가겠습니다. 🙂

'💻 프로그래밍 > 🅽 Node.js' 카테고리의 다른 글

null 속성값 치환 알고리즘  (0) 2022.10.30
Factory Design Pattern 2  (0) 2022.09.23
Retry Promise Design Pattern  (0) 2022.09.20
Promise.all 병렬 실행  (0) 2022.09.19
return vs return await 함정  (0) 2022.09.19

이미지출처: retry

이번에 작성하는 포스트는 최근 현업에서 서비스를 개발하는데 알아보았던 Promise 재시도 디자인패턴에 관한 것입니다.

서비스를 개발할적에 우리는 빈번하게 서드파티 API 를 사용하게 됩니다. 제가 개발하고 있는 서비스는 AWS 와 토스 페이먼츠 등을 사용하고 있습니다. 서드파티 측에서 문제가 발생되어 잠시동안 API 호출이 안되는 경우가 존재할 수 있다면 우리는 이것에 어떻게 대처해야 할까요?

저는 이러한 경우 API 호출을 재시도 하는 방식으로 해결해야 겠다고는 생각했지만 .. 사용성과 관리가 용이한 방식을 잘 알지 못했습니다. 그러다 최근 괜찮다고 생각되는 방법을 알게 되어 글을 작성합니다.

사실 비동기 스트리밍은 선언적 프로그래밍 형태를 가진 Rxjs 라이브러리를 사용하는게 좋은 선택으로 보였지만 현재 프로젝트에서는 순수 Promise 를 기반으로 API 와 DB를 호출하고 있어 가능하다면 이를 유지하는 방향으로 통일성을 지키고 싶었습니다.

결국 API 호출 로직을 감싸는 방식을 사용하였는데요.

코드를 살펴보면..

// PromiseService.js

class PromiseService {
  static #instance
  
  static getInstance() {
    if (!this.#instance) {
      this.#instance = new PromiseService()
    }
    return this.#instance
  }
  
  /**
   * @param promise A promise to resolve
   * @param nthTry Number of tries before rejecting
   */
  retryPromise(promise = () => {}, nthTry = 1) {
    try {
      const data = promise()
      return data
    } catch (e) {
      if (nthTry === 1) { return Promise.reject(e) }
      return this.retryPromise(promise, nthTry - 1)
    }
  }
  
  /**
   * Util function to return a promise which is resolved in provided milliseconds
   */
  waitFor(milliSeconds = 1000) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, milliSeconds)
    })
  }
  
  async retryPromiseWithDelay(promise = () => {}, nthTry = 1, delayTime = 1000) {
    try {
      const res = promise()
      return res
    } catch (e) {
      if (nthTry === 1) { return Promise.reject(e) }
      await this.waitFor(delayTime)
      return this.retryPromiseWithDelay(promise, nthTry - 1, delayTime)
    }
  }
}

module.exports = PromiseService

서비스 파일에서는 호출 재시도(retruPromise, retryPromiseWitHDelay) 메서드를 제공하고 재시도 횟수만큼 재귀하는 방식으로 구현하였습니다.

사용 코드를 살펴보면..

async function main() {
  try {
    const promiseService = PromiseService.getInstance()
    const resposne = await promiseService.retryPromiseWithDelay(
      () => axios.get('https://api.twitter.com/v1/user/1234'), // promise
      4, // retry
      2000 // delayTime
    )
    return response
  } catch (e) {
    throw e
  }
}

main()


단순하게 선언적으로 Promise 와 파라미터만 입력하면 되기때문에 사용법이 쉽고 관리가 용이한 방식 같습니다.

'💻 프로그래밍 > 🅽 Node.js' 카테고리의 다른 글

Factory Design Pattern 2  (0) 2022.09.23
Factory Design Pattern 1  (0) 2022.09.21
Promise.all 병렬 실행  (0) 2022.09.19
return vs return await 함정  (0) 2022.09.19
Node.js 철학  (0) 2022.09.19

이미지출처: rajatexplains

async / await 를 사용하여 일련의 작업들을 병렬로 실행하는 방법은 크게 2가지가 있습니다.
하나는 순수하게 await 표현식을 사용하는 것이고 다른 하나는 Promise.all()에 의존하는 것입니다.
2가지 모두 구현하기 간단합니다. 그러나 Promise.all() 을 사용하는 방법을 권장합니다. 🙂

우선 순수하게 await 표현식을 사용해서 무제한 병렬 비동기 실행 흐름을 구현하려면 다음과 같은 코드로 구현할 수 있습니다.

async function spiderLinks(currentUrl, content, nesting) {
  if (nesting === 0) { return } 
  
  const links = getPageLinks(currentUrl, content)
  const promises = links.map(link => spider(link, nesting - 1))
  for (const promise of promises) {
    await promise
  }
}

매우 간단합니다. 이게 전부입니다. 앞의 코드에서 모든 spider() 작업을 병렬로 시작하였고 map()을 사용하여 실행된 작업들의 Promise를 수집합니다. 그리고 나서 Loop 를 돌며 각각의 Promise 를 await 합니다.

처음엔 이것이 깔끔하고 기능적으로 보이지만 생각하지 않은 부작용이 있습니다. 바로 promises 배열에 어떤 Promise 가 문제가 발생하여 거부가되면 spiderLinks()에서 반환된 Promise도 거부되게 되는데 배열내의 모든 선행 Promise들이 해결될 때까지 기다려야 합니다.
일반적으로 작업의 실패를 최대한 빨리 알기 원하기 때문에 이것은 모든 상황에서 최선의 방법이 아니고 비효율적인 방법입니다.

다행히도 원하는 방식으로 정확하게 동작하는 Promise.all() 이라는 내장 함수로 문제를 해결 할 수 있습니다.
실제로 Promise.all() 은 입력 배열에 제공된 Promise 중 하나가 거부되는 즉시 거부합니다.
그리고 Promise.all() 은 또 다른 Promise만 반환하기 때문에 여러 비동기 작업에서 결과를 얻기 위해 await 을 사용할 수 있습니다.

const results = await Promise.all(promises)

결론적으로 병렬 실행 및 async / await 를 사용하는 spiderLinks() 함수의 구현은 다음과 같이 작성 할 수 있습니다.

async function spiderLinks(currentUrl, content, nesting) {
  if (nesting === 0) { return }
  
  const links = getPageLinks(currentUrl, content)
  const promises = links.map(link => spider(link, nesting - 1))
  return Promise.all(promises)
}

처음 로직과 다르게 Promise.all() 함수를 사용했으며 이는 promises 배열에 어떤 Promise 가 문제가 발생하여 거부가 발생되면 즉시 거부 되도록 처리되므로 효율적으로 동작됩니다.

'💻 프로그래밍 > 🅽 Node.js' 카테고리의 다른 글

Factory Design Pattern 2  (0) 2022.09.23
Factory Design Pattern 1  (0) 2022.09.21
Retry Promise Design Pattern  (0) 2022.09.20
return vs return await 함정  (0) 2022.09.19
Node.js 철학  (0) 2022.09.19

이미지출처: chargebacks911


오늘은 실무에서 자칫하면 실수 할 수 있는 비동기 이해에 관한 글을 작성합니다.
return Promise 에 대한 내용인데 이는 개발자들이 빈번하게 실수하는 흔한 안티패턴 중 하나입니다.

내용은 즉 async / await와 함께 에러를 다룰 적에 호출자에서 Promise 를 반환하게 하고 Promise 를 반환하는 함수의 로컬 try...carch 블록에서 에러가 잡히는 것을 기대하는 것입니다.

코드로 살펴보면

async function errorNotCaught() {
  try {
    return delayError(1000)
  } catch (err) {
    // 영원히 호출되지 않는다
    console.error('Error caught by the asycn function: ' + err.message)
  }
}

errorNotCaught()
	.catch(err => console.log('Error caught by the caller: ' + err.message))
    
// 실행결과: Error caught by the caller: Error after 1000ms

delayError() 에 의해 반환되는 Promise는 로컬에서 기다리지(await) 않습니다. 즉, 호출자에게 반환되고 결과적으로 로컬의 catch 블록은 영원히 호출되지 않습니다.

만약 호출자가 비동기 작업에 의해서 발생하는 값뿐만 아니라 에러까지 로컬에서 포착할 수 있도록 만들고 싶다면, 반환될 Promise 앞에 await 표현식을 사용해야 합니다.

async function errorNotCaught() {
  try {
    return await delayError(1000)
  } catch (err) {
    console.error('Error caught by the asycn function: ' + err.message)
  }
}

errorNotCaught()
	.catch(err => console.log('Error caught by the caller: ' + err.message))
    
// 실행결과: Error caught by the asycn function: Error after 1000ms

return 키워드 뒤에 await 을 추가하는 것 만으로 async 함수가 Promise를 로컬에서 처리하게 되므로 문제를 해결 할 수 있습니다. 🙂

'💻 프로그래밍 > 🅽 Node.js' 카테고리의 다른 글

Factory Design Pattern 2  (0) 2022.09.23
Factory Design Pattern 1  (0) 2022.09.21
Retry Promise Design Pattern  (0) 2022.09.20
Promise.all 병렬 실행  (0) 2022.09.19
Node.js 철학  (0) 2022.09.19

+ Recent posts