페이지를 가져온 뒤 css, image, font를 차단하면 더 빠른 DOM 액세스가 가능하다.

리소스 차단

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// @types/puppeteer
import { launch, Browser, Request, Page } from 'puppeteer';

const browser: Browser = await launch({
headless: true,
});

const page: Page = await browser.newPage();
await page.setRequestInterception(true);

page.on('request', (req: Request) => {
switch (req.resourceType()) {
case 'stylesheet':
case 'font':
case 'image':
req.abort();
break;
default:
req.continue();
break;
}
});

await page.goto('URL');

발단

서비스워커는 n:m (사이트:디바이스)로 모든 클라이언트 기기에 설치된다.
배포 후 아무 문제 없이 동작하였으나, 190604 이후 웹뷰에서 net::ERR_ABORTED 페이지가 보인다는 버그가 들어오기 시작했다.
랜덤하게 발생되고 있어 추적이 어려웠으나 4일에 Chrome의 메이저 버전이 업데이트 되었다라는 것을 확인했고 디버깅을 시작했다.

디버깅

서비스워커가 내려오지 않은 경우 페이지가 100% 정상동작을 하였다.
서비스워커가 내려온 경우 랜덤하게 페이지가 로드되지 않았다.

문제는 서비스워커로 확인되었고, 세부적인 디버깅 내역은 다음과 같다.

  • fetchListener 내부 cacheStorage 접근 예외처리: 재현
  • fetchListener 제거: 재현 안됨

Chrome 75버전의 웹뷰에서 서비스워커의 fetching 방식이 변경되었다는 걸 확인할 수 있었다.
74, 75버전의 Diff를 찾을 수 있어 서비스워커 코어가 어마어마하게 변경되었다는 걸 확인할 수 있었으나 이 코드를 디버깅하는 것보다 퇴사 후 행복하게 사는 게 멋질 것이란 판단이 들었다.
다행히 구글러와 연락이 닿아 private 버그리포팅을 했고, Chrome은 오픈소스라 구글에서도 일일히 확인하기 힘들다라는 답변을 들을 수 있었다.

웹뷰와 서비스워커

웹뷰의 서비스워커와 브라우저의 서비스워커는 다른 인스턴스이므로 서로 공유되지 않는다.
그렇다면 웹뷰에서 재접속시에 앱 셸을 빠르게 로드하는 이점 뿐이라는 말이다. === 어드벤티지 없음

WebView UA in Lollipop and Above에 따르면 안드로이드 롤리팝 이후부터 User Agent에 wv란 값을 물고 들어온다.

해결

예외처리

Chrome 엔진이 업데이트 된다고 서비스워커가 제거되지 않는다.
따라서 UA 에 android, wv 값이 있는 경우 서비스워커를 설치하지 않을 뿐 아니라 설치된 서비스워커를 제거해주는 로직이 있어야한다.
아니라면 A way to immediately unregister a service worker 기능을 브라우저 벤더들이 개발해줘야한다.

더 특정한 버전을 줘서 예외처리를 한다면 다음과 같을 것이다.

Chrome 75.0.3770.67 ~ 75.0.3770.101 버전의 모든 안드로이드 웹뷰에서 서비스워커 설치를 차단, 이미 설치가 되어있다면 삭제

패치

몇일 뒤에 다른 업체에서 public 하게 버그리포팅을 올렸고 버그를 찾아서 조만간 패치될 예정이다.

This is affecting tens of thousands of our readers 로 보아 나와 같은 빡침이 느껴져서 아련했다.

패치된 코드는 여기서 볼 수 있다.

여담

  • 브라우저에 버그 발생시 대처하는 방법은 거의 불가능하다.
  • 실수는 여기든 저기든 다 똑같구나

맥에서 구글 계정 연동 중에 Google 계정 인증에 실패했다는 오류메세지가 나온다면 다음과 같이 해결하면 된다.

해결

설정 > 일반 > 기본 웹브라우저Safari.app으로 변경한다.

구글링시 아래 기능들을 해제해보라는데 다 쓸모없다.

  • 2차 인증 해제
  • 앱 비밀번호 사용
  • 보안되지 않은 앱 허용

타임존 데이터는 php의 버전을 따라 올라가는데, 실무에선 항상 최신버전을 사용하기 쉽지 않다.
그럴 때 데이터만 업데이트하는 방법을 쓸 수 있다.

설치

2019.06 현재 최신 타임존 데이터베이스는 2019a (2019.01) 버전이다.

perl

perl로 설치되는 일반적인 경우는 아래와 같이 쉽게 설치 가능하다.

1
2
3
4
# perl 로 설치
$ perl install timezonedb

# 끝!

phpize

그렇지 않은 경우 라이브러리를 수동으로 빌드해줘야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 타임존 데이터 다운로드
$ curl -LO https://pecl.php.net/get/timezonedb-2019.1.tgz

# 압축 해제
$ tar -xvzf timezonedb-2019.1.tgz
$ cd timezonedb-2019.1

# 빌드
$ phpize
$ ./configure --with-php-config=${PHP_CONFIG_PATH}
$ make && make install

# 라이브러리를 extensions 폴더로 이동
$ mv timezonedb.so /usr/local/php/extentions

# 라이브러리 추가
$ vi php.ini
$ extension=timezonedb.so

# 아파치 재시작
$ apachectl restart

# 버전 확인
$ /usr/bin/php -r "echo timezone_version_get();"
2019.01

참조

  • Compiling shared PECL extensions with phpize

usb 디버깅 승인 후 chrome://inspect#devices 탭에 접근하면 기기가 보이면서 디버깅이 가능해야하는데,
그렇지 못한 경우에 아래처럼 진행해주면 된다.

해결

먼저 usb 디버깅 권한을 모두 초기화하고 다시 연결한다.

1
2
3
# 디버깅 서버를 죽인 후 다시 실행한다.
$ adb kill-server
$ adb usb

개발자도구의 More tools > Remote devices의 목록에서 확인할 수 있다.

여담

  • PTP 연결을 하라는데 이건 안드로이드 구버전에 대한거라 필요 없다.
  • 웹뷰 디버깅시에는 디버깅 권한이 있는 앱이여야 가능하다.

매번 용어 찾아보는 게 귀찮아서 정리했다.
어떤 기능을 yaml 로 구성해야하는지 감이 오는 듯하다.

노드

  • 컨테이너가 배치되는 서버
  • 쿠버네티스 클러스터의 관리 대상으로 등록된 도커 호스트
  • 쿠버네티스 클러스터는 마스터와 노드의 그룹으로 구성
  • kubectl get nodes

마스터 노드

  • 스웜의 매니저노드와 비슷한 느낌이다.
  • kube-apiserver: 쿠버네티스 API를 노출하는 컴포넌트 kubectl로부터 리소스를 조작하라는 지시를 받음
  • ectd: 고가용성을 갖춘 분산 Key/Value 스토어로 쿠버네티스 클러스터의 백킹스토어로 쓰인다. (CAS)
  • kube-scheduler: 노드를 모니터링하고 컨테이너를 배치할 적절한 노드탐색
  • kube-controller-manager: 리소스를 제어하는 컨트롤러 실행

일반적인 (non-managed, GKE 등이 없는) 환경에선 마스터 노드서버가 단일 장애지점 (SPOF)가 되지 않도록 마스터를 3대 두는 것이 필수다.

네임스페이스

  • 쿠버네티스 클러스터 안의 가상 클러스터
  • 네임스페이스마다 권한을 줄 수 있으므로 개발팀이 클 때 유용하다.
  • 기본으론 default, docker, kube-public, kube-system 네임스페이스가 있다.
  • kubectl get namespace

파드

  • 컨테이너의 집합 중 가장 작은 단위로, 컨테이너의 실행 방법을 정의
  • 적어도 하나 이상의 컨테이너로 이뤄져있다.
    • nginx reverse proxy + application web server
  • 파드는 노드 안에 들어간다.
  • 함꼐 배포해야 정합성, 일관성이 유지된다면, 같은 파드로 묶어 배포한다.
  • 파드엔 각각 고유의 가상 IP가 할당된다.
  • kubectl get pod

레플리카세트

  • 같은 스펙을 갖는 파드를 여러 개 생성/관리 하는 역할
  • 똑같은 파드의 레플리케이션 개수를 관리하는 리소스이다.
  • 삭제된 파드는 복원할 수 없기 때문에 웹 어플리케이션과 같은 stateless 파드에 사용하기에 좋다.
  • kubectl get replicaset
  • 레플리카셋의 파드명은 container-1b2e3f 처럼 임의로 정해진다.

디플로이먼트

  • 레플리카세트의 리비전을 관리 (빠른 버저닝)
  • 리비전은 컨테이너 이미지가 수정된 경우에 생성된다.
  • kubectl rollout history deployment ..

데몬셋

  • 모든 시스템에서 실행되어야하는 파드와 템플릿 제공

서비스

  • 파드의 집합에 접근하기 위한 경로를 정의
  • 쿠버네티스 클러스터 내에서 파드의 집합에 대한 경로나 서비스 디스커버리를 제공하는 리소스
  • 서비스의 대상이 되는 파드는 서비스에서 정의하는 레이블 셀렉터로 정해진다.
  • 서비스의 접근은 같은 네임스페이스라면 서비스명만으로 접근이 가능하다.
    • http://${svc} => http://${svc}.default => http://${svc}.default.svc.local

ClusterIP 서비스

  • 서비스의 기본 타입
  • 쿠버네티스 클러스터의 내부 IP 주소에 서비스를 공개할 수 있다.
  • 다른 파드 그룹으로 접근할 때 서비스를 거쳐 가도록 할 수 있으며, 서비스명으로 네임 레졸루션이 가능해진다.
  • 외부에서는 접근할 수 없다.

NodePort 서비스

  • 쿠버네티스 클러스터 외부에서 접근할 수 있는 서비스
  • 서비스를 L4 레벨에서 노출하는 것이므로 TCP/UDP 모두 다룰 수 있다.
  • kubectl get svc ...

LoadBanlancer 서비스

  • 로컬 쿠버네티스 환경에선 사용할 수 없다.

ExternalName 서비스

  • 쿠버네티스 클러스터에서 외부 호스트를 네임 레졸루션하기 위한 별칭이다.
  • gracefullighut.dev 를 gracefullight 로 접근 가능하게 만든다.

인그레스

  • 서비스를 쿠버네티스 클러스터 외부로 노출
  • NodePort 서비스는 L4라 HTTP/S의 경로기반 라우팅이 불가능한데, 인그레스는 L7 레벨로 제어가 가능하다.
  • 주로 HTTP/S 서비스를 노출하는 경우 사용한다.
  • 로컬 쿠버네티스 환경에서는 인그레스를 사용해 서비스를 노출시킬 수 없으므로 nginx ingress controller 를 사용한다.

컨피그맵

  • 설정 정보를 정의 후 파드에 전달

퍼시스턴트볼륨

  • 파드가 사용할 스토리지의 크기 및 종류를 정의

퍼시스턴트볼륨 클레임

  • 퍼시스턴트 볼륨을 동적으로 확보

스토리지클래스

  • 퍼시스턴트 볼륨이 확보하는 스토리지의 종류를 정의

스테이트풀세트

  • 같은 스펙으로 모두 동일한 파드를 여러 개 생성하고 관리
  • 스테이트풀셋의 파드명은 backend-0, backend-1 처럼 인덱스로 정해진다.

  • 일회성 파드를 여러 개 생성하고 종료 보장

시크릿

  • 인증 정보 등 비밀 데이터 정의

  • 네임스페이스 안에서 조작 가능한 쿠버네티스 리소스의 규칙 정의
  • 각 쿠버네티스 API 사용 권한 정의
  • 지정된 네임스페이스 내에서만 유효

롤 바인딩

  • 쿠버네티스 리소스 사용자와 롤을 연결

RBAC

  • Role-based access control (RBAC) is a method of regulating access to computer or network resources based on the roles of individual users within an enterprise.
  • Docs

권한 디버깅

1
2
3
4
5
# can-i 문으로 권한 확인 가능
$ kubectl auth can-i ...

# kubectl auth can-i create deployments
# yes

클러스터롤

  • 클러스터 전체적으로 조작 가능한 쿠버네티스 리소스의 규칙 정의
  • 클러스터 전체에서 유효

클러스터롤 바인딩

  • 쿠버네티스 리소스 사용자와 클러스터롤을 연결
  • 일반 사용자 및 그룹/서비스 계정과 클러스터롤을 연결

서비스 계정

  • 파드가 쿠버네티스 리소스를 조작할 때 사용하는 계정
  • 구글의 서비스 계정 비슷한 듯

노드어피니티

  • 노드 셀렉터보다 더 세밀한 선택이 가능 (스케줄링 정책 조건 등)

테인트

  • taint
  • 오염 조건을 추가해 노드 스케줄링에서 제외
  • 파드를 특정 노드로 강제할 수 있다.

컨트롤 플레인

  • kubelet, kubenetes master 등 컨트롤 플레인은 시스템 내 모든 쿠버네티스 오브젝트의 레코드를 유지하면서, 오브젝트의 상태를 관리하는 제어 루프를 지속적으로 구동

Dynamic Admission Control

  • 웹훅으로 동작
  • ValidationWebhookConfiguration
  • MutatingWebhookConfiguration

클러스터 데몬

  • 클러스터 모든 서비스에 검사, 실행 필요시 사용

클러스터어시스턴스

  • SSL 인증 배포 등 자동화
  • cert-manager

lsd

ls -al

LSDeluxe 커맨드로 ls 명령어를 예쁘게 변경해보자.

설치

1
2
3
4
5
6
7
8
9
10
# 설치
$ brew install lsd

# alias 설정
$ vi ~/.zshrc

alias ls='lsd'
alias ll='ls -alhF'

$ source ~/.zshrc

설치는 했지만 아이콘이 깨져서 보일 것이다.

글꼴 설정

1
2
3
# 글꼴 설치
$ brew tap homebrew/cask-fonts
$ brew cask install font-hack-nerd-font

iTerm2 > Preferences > Profiles > Text 탭으로 이동해 Non-ASCII Font를 확장한 뒤 방금 설치한 Hack Regular Nerd Fonr Compelete 폰트를 설정해주자.

Use ligatures 옵션 또한 체크한다.

Non-ASCII Font 설정

triple des 알고리즘으로 암호화하는 일은 요새는 드문데, 드물어서 그런지 구글링해도 아무 것도 나오지 않았다.

레거시 언어들에는 3des 암호화된 로직이 많은데, 포팅하면서 개발해야될 필요성이 생겼다.

암호화

  • nodejs 의 암호화 패키지는 crypto 안에 들어있다.
  • crypto.getCiphers(); 메소드로 사용할 수 있는 알고리즘을 확인할 수 있다.
    • ciphers.filter(cipher => cipher.includes('des'));
  • 3des 알고리즘은 des-ede3 로 시작한다.
  • 키는 24bytes 이며, iv 는 8bytes 다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const crypto = require('crypto');

class TripleDes()
{
// #iv;
// #key;

constructor(key, iv) {
this.key = key;
this.iv = iv;
}

getKey() {
return this.key.padEnd(24, String.fromCharCode(0));
}

getIv() {
return this.iv;
}

encrypt(plain, iv) {
if (!iv) {
iv = this.iv ? Buffer.from(this.iv, "hex") : crypto.randomBytes(8);
}

const cipher3des = crypto.createCipheriv("des-ede3-cfb8", this.getKey(), iv);
let encrypted = cipher3des.update(plain, "utf8", "hex");
encrypted += cipher3des.final("hex");

this.iv = iv.toString("hex");
return encrypted;
}

decrypt(encrypted, iv) {
if (!iv) {
iv = this.iv;
}

const decipher3des = crypto.createDecipheriv("des-ede3-cfb8", this.getKey(), Buffer.from(iv, "hex"));
let decrypted = decipher3des.update(encrypted, "hex", "utf8");
decrypted += decipher3des.final("utf8");

return decrypted;
}
}

module.exports = TripleDes;

위의 encrypt 메소드는 아래처럼 버퍼를 합친 후에 헥스로 바꾸는 것과 동일하다.

1
2
3
4
5
6
let encrypted = Buffer.concat([
cipher3des.update(plain, 'utf8'),
cipher3des.final('utf8'),
]);

encrypted = encrypted.toString('hex');

사용법

1
2
3
4
5
6
7
const TripleDes = require('./TripleDes');

const tripleDes = new TripleDes('encryptionKey');
// 암호화
const encrypted = tripleDes.encrypt('yummy');
// 복호화
const decrypted = tripleDes.decrypt(encrypted);

참조

  • nodejs.crypto

도메인 구입

Google Domains 에서 구입한다.

  • 미국 주소가 있어야된다는 말이 나오는데, 무시하고 구매하기를 하자.
  • 도메인 기관에서 연락이 갈 수 있다는 내용에 주소를 대한민국으로 변경 후 입력해주고
  • 결제지 우편번호는 대충 쓰고 미국으로 하고 넘어가면 쉽게 구매할 수 있다.

DNS 설정

DNS > 맞춤 리소스 레코드 탭에서 Github Domain IP 를 A 레코드로 추가한다.

  • 185.199.108.153
  • 185.199.109.153
  • 185.199.110.153
  • 185.199.111.153

http 예외

http 로 서빙하는 경우가 있다면 www.gracefullight.dev 도 사용할 수 있게 CNAME 을 등록하자.

1
www CNAME 1h gracefullight.github.io.

물론 HSTS 를 적용하는 경우 필요없다.

CNAME 파일 추가

public 경로 아래 CNAME 파일을 생성 후 도메인을 적는다.

CNAME
1
gracefullight.dev

github pages 설정

github.com/gracefullight/gracefullight.github.io/settingsGitHub Pages 탭으로 이동해
Custom domain 에 설정할 도메인을 넣고, Enforce HTTPS 를 체크한다.

아래 문구가 보이면 성공한 것이다.

Your site is published at https://gracefullight.dev/

웹마스터 설정

Google

구글 검색에서 도메인 변경을 다시 인덱싱해주기 위해 마이그레이션이 필요하다.
새로운 사이트를 등록해 준 뒤 이전 버전의 서치콘솔에서 기존 사이트의 설정버튼을 누르면 바로 설정이 가능하다.

주소 변경

기타

네이버와 빙은 같은 site verification 코드가 나와서 사이트만 추가해주면 되는데,
얀덱스의 경우는 코드를 변경하고 빌드해줘야한다.

여담

  • CNAME 변경하면 Github setting의 Custom domain이 빠지기 때문에 다시 등록해줘야하는 번거로움이 있다.
  • 애널리틱스는 도메인만 바꿔주면 바로 연동된다.
  • 애드센스가 도메인 승인이 2주가 걸려서 그전까진 애드-프리 사이트가 될 듯하다.

RxJS

리액티브 프로그래밍은 개인적으로 비동기 프로그래밍과 함수형 프로그래밍의 종착지라고 생각한다.
Stage1 Draft로 제안되어 더 이상 피할 수 없는 Observable 을 알아보자.

정의

위키피디아에선 다음과 같이 정의되어있다.

reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change.
데이터 스트림과 변화의 전파에 중점을 둔 프로그래밍 패러다임

2011년 태초에 RX 의 개념을 만든 MS 문서에서는 Reactive eXtensions 다음과 같다.

Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators.
옵져버블 시퀀스와 링큐 쿼리 연산자를 사용하는 비동기, 이벤트 기반 프로그래밍 라이브러리

조금 더 디테일하게 말하면 데이터스트림을 Pulling 방식의 이터레이터 패턴인 IEnumerable<T>/IEnumerator<T> 로 만들어 Pushing 방식의 옵저버 패턴인 IObservable<T>/IObserver<T>로 전파/구독하는 것이다.

Rx를 모두 이해한 뒤 이 정의를 보면 어쩜 이렇게 깔끔하게 한 줄로 이 내용을 다 담았을까? 란 생각이 드는데, 처음보는 입장에선 비동기인 건 알겠네 정도로만 이해가 되는 듯하다.
어려운 게 당연하다. 멀티쓰레드 프로그래밍을 처음 배울 때의 감정을 생각해보자.

용어

  • 비동기 프로그래밍: 이 문서를 볼 정도면 설명이 필요 없을 것 같다.
  • 함수형 프로그래밍: 함수를 FirstClass 로 취급해 파라미터, 리턴, 변수에 할당 가능하며 함수 합성이 가능하고 원하는 시점에 호출이 쉽다.
  • 데이터스트림: file stream, event stream, http stream 의 친구로 data 식 표현
  • 이터레이터: 설명 필요 없을 듯.
  • 옵져버: 디자인패턴 책의 옵져버패턴 단원 참조, 자바스크립트에선 이미 이벤트리스너가 옵져버 패턴이다.
  • 옵져버블: 특정 객체를 관찰하는 옵저버에게 여러 이벤트나 값을 전파하는 역할
  • 풀: 데이터를 받을지 결정
  • 푸쉬: 데이터를 보낼지 결정
  • 싱글: 하나의 값이나 이벤트를 다룸
  • 멀티플: 여러 개의 값이나 이벤트를 다룸

이 모든 용어를 하나의 표로 정리하면 다음과 같다.

싱글멀티플
함수이터레이터
푸쉬프로미스옵져버블

패턴

  • 옵져버블의 변수 스타일은 이름 뒤에 const click$ 처럼 $을 뒤에 붙혀주는게 정형화되어있다.
  • subscribenext, error, complete를 파라미터로 받는다.

마블 다이어그램

마블 다이어그램은 연산자를 쉽게 이해하기 위해, 옵져버블을 테스트하기위해 도식화된 다이어그램이다.
타이밍과 값의 변화를 한 눈에 파악할 수 있다.

map

  • 위에 줄은 input 이고 연산자를 만나고나면 아래 줄에서 output 이 어떻게 바뀌는지 보여진다.
  • 가로 줄은 하나의 옵져버블이다.
  • 줄의 | 파이프는 구독 완료를 나타낸다.

소스

생성

가장 간단한 옵져버블을 만들고 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const { Observable } = require('rxjs');

const test$ = Observable.create((observer) => {
console.log('create');
observer.next(1);
observer.next(2);
observer.complete();
console.log('done');
});

test$.subscribe(
(item) => {
console.log(item);
},
(error) => {},
() => {
console.log('complete');
}
);

/*
create
1
2
complete
done
*/

간단하지만 이터레이터이면서 구독가능하다는 걸 확인할 수 있다.

구독해제

옵져버블의 리턴함수로 구독해제 콜백을 지원한다, 콜백이 필요하지 않다면 unsubscribe() 를 호출해주기만 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
const test$ = Observable.create((observer) => {
const interval = setInterval(() => {
console.log('test');
}, 1000);

return () => {
clearInterval(interval);
};
});

const subscription = test$.subscribe();
subscription.unsubscribe();

파이퍼블 연산자

Pipeable 연산자는 옵져버블 인스턴스를 pipe 함수 안에서 다룰 수 있는 연산자이다.
기본적으로 rxjs/operators 라이브러리 안에 들어있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { map } = require('rxjs/operators');

const test$ = Observable.create((observer) => {
observer.next(1);
observer.next(2);
observer.complete();
});

test$.pipe(map((value) => value * 2)).subscribe((item) => console.log(item));

/*
2
4
*/

소스 옵져버블에서 발행된 값을 원하는대로 바꿀 수 있다.

연산자

of

args 순서대로 값을 반환한다.

from

  • Observable
  • Array
  • Promise
  • Iterable
  • String
  • ArrayLike

위 타입을 옵져버블로 변환해준다.

fromEvent

EventEmitter 클래스의 객체와 조합하거나 브라우저의 이벤트를 옵져버블로 바꿀 때 사용한다.

defer

팩토리 함수로 옵져버블을 생성한 후 구독한느 시점에 팩토리 함수를 호출해 이미 생성한 옵져버블을 리턴받아 구독한다.

from 과의 차이는 다름과 같다.

  • from
    • 프로미스 내부 구현부가 언제 실행되던지 상관 없을 때
    • 이미 실행 중이거나 완료한 프로미스를 옵져버블로 만들 때
  • defer
    • 옵져버블을 구독하는 시점에 프로미스를 생성해 구현부가 실행되어야할 때
    • 프로미스 객체 생성 시점이 구독하는 시점이여야할 때

range

범위 지정 후 그 값을 순서대로 발행한다. 반복문이 필요할 때 사용된다.

interval

ms 단위로 값을 발행한다.

timer

파라미터가 하나일 경우 ms 이후에 한 번 값을 발행하고,
두 개일 경우 ms 이후에 두번 째 파라미터만큼 주기적으로 값을 발행한다.

empty

값 발행 후 중간에 멈춰야하는 상황에 사용한다. 이 함수만 사용하지는 않고 다른 함수나 연산자와 조합해서 complete 함수를 호출해야 할 때 사용된다.
즉, 바로 구독을 완료해야될때 사용된다.

1
2
// 상수로 사용된다.
const { EMPTY } = require('rxjs');

never

아무 것도 하지 않고 옵져버블 생성이 필요할 때 사용된다.

1
2
// 상수로 사용된다.
const { NEVER } = require('rxjs');

throwError

옵져버블로 값을 발행하다가 에러를 발생시키고 종료해야하는 상황에 사용한다.

filter

주로 파이퍼블 연산자와 연결해서 사용된다.

1
2
3
4
5
const { filter } = require('rxjs/operators');
// 1~10 중 짝수 필터
range(1, 10)
.pipe(filter((x) => x % 2 === 0))
.subscribe((x) => console.log(x));

first

처음으로 일치하는 값을 발행한다. 두 번째 인자로 기본 값을 줄 수 있다.

last

마지막으로 일치하는 값을 발행한다. 두 번째 인자로 기본 값을 줄 수 있다.

take

정해진 갯수만큼 구독하고 구독을 해제한다.
interval 과 같이 무한 반복이 실행되는 연산자와 같이 쓰면 된다.

1
2
3
4
5
6
7
8
9
10
11
const { take } = require('rxjs/operators');

interval(1000)
.pipe(take(3))
.subscribe((x) => console.log(x));

/*
0
1
2
*/

takeUntil

특정 이벤트가 발생할 때까지 옵져버블을 구독해야할 때 사용한다.
예시로 보는 게 빠르다.

1
2
3
4
5
6
interval(1000)
.pipe(
take(100),
takeUntil(fromEvent(document.querySelector('#btn'), 'click'))
)
.subscribe((x) => console.log(x));

takeWhile

take 와 filter 가 합쳐진 연산자이다.

1
2
3
interval(1000)
.pipe(takeWhile((x) => x <= 10))
.subscribe((x) => console.log(x));

takeLast

Last 의 파라미터 수 만큼 저장해뒀다가 구독 완료시에 일괄적으로 발행한다.
발행하는 값이 [0, 2, 4, 6, 8, 10] 일 때 takeLast(3) 일 경우 내부에 저장된 값은 다음과 같다.

발행값내부배열
0[0]
2[0, 2]
4[0, 2, 4]
6[6, 2, 4]
8[6, 8, 4]
10[6, 8, 10]

skip

이름 그대로 n 개만큼의 발행을 건너 뛴다.

skipUntil

takeUntil 과의 반대로 옵져버블이 실행될 때까지 건너 뛴다.

1
2
3
4
5
6
7
8
9
const time = 1000;
interval(time)
.pipe(skipUntil(interval(time * 5)), take(2))
.subscribe((x) => console.log(x));

/*
4
5
*/

skipWhile

조건을 만족하지 않는 순간부터 값을 발행한다.

debounce

많이 사용하진 않지만, 디바운스할 옵져버블을 계산값을 리턴해주는 함수를 파라미터로 주면 된다.

debounceTime

로대쉬나 다른 라이브러리의 debounce 와 같다.

distinct

중복은 제거하고 발행한다. 값 비교에는 === 연산자가 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
of({ id: 1 }, { id: 1 }, { id: 2 }, { id: 3 })
.pipe(
distinct((data) => data.id),
map((data) => data.id)
)
.subscribe((x) => console.log(x));

/**
1
2
3
*/

두번째 파라미터로 flush 조건을 옵져버블로 넘겨 중복 조건을 초기화시킬 수 있다.

distinctUntilChanged

중복값이 연속으로 발행된 경우만 제거한다.

1
2
3
4
5
6
7
8
9
10
11
of(1, 2, 3, 3, 4, 1)
.pipe(distinctUntilChanged())
.subcribe((x) => console.log(x));

/**
1
2
3
4
1
*/

첫번째 파라미터로는 비교함수 (prev, next 를 파라미터로 받는)를 넣어 연속 비교조건을 변경할 수 있다.

두번째 파라미터로는 비교할 값 셀렉터를 변경해줄 수 있다.

1
2
3
4
5
6
7
8
9
10
of(
{ a: 1, b: 10 },
{ a: 1, b: 10 },
{ a: 2, b: 20 },
{ a: 3, b: 30 },
{ a: 3, b: 30 },
{ a: 2, b: 20 }
)
.pipe(distinctUntilChanged(null, (data) => data.a))
.subscribe((x) => console.log(x));

sample

이건 마블 다이어그램으로 이해하는 게 빠르다.

sample

notifier 옵져버블(x 옵져버블)이 발행되면 이전 최근 값을 발행한다.
값 c 처럼 소스 옵져버블에 새로운 값이 없을 경우 값을 중복으로 발행하지 않는다.

sampleTime

일정 간격 사이에 있는 최근 값을 발행한다.

pluck

기본 map 연산자는 뻔하기에 적지 않았다.
map 처럼 동작하지만 소스 옵져버블에서 객체를 리턴할 때 객체의 property 를 뽑아낸다.

mergeMap

switchMap

concatMap

scan

partition

groupBy

buffer

bufferCount

window

windowCount

여담

  • RxJS 의 장점은 이벤트 구독을 취소하고, 모든 이벤트를 하나의 스트림으로 제어할 수 있는 것이라고 생각한다.
  • 이해하는데 상당한 기간이 소요되었지만, 웹에선 다음과 같은 상황에서 사용될 것 같다.
    • SPA
    • 에디터와 같이 많은 컴포넌트끼리 통신이 필요할 때
  • 이 상황에서도 앵귤러가 아닌 스토어 기반의 리액트, 뷰를 사용한다면 RxJS를 쓸거야 라고 보여주기 위해 사용하는 느낌이 든다.
  • 모두가 같이 가기엔 꽤 어려운 개념이다.
  • 싱글 스레드 기반의 백엔드에서 더 유용하게 쓰일 수 있을 듯
  • learn-rxjs의 예제를 분석하는 게 더 많은 도움이 될 것 같다.