Archive

Typescript Day 08 - Promise

|

Typescript Day 08 - Promise


Promise

타입스크립트에서 프로미스는 제네릭 클래스 형태로 사용한다.

const strPromise: Promise<string> = new Promise<string>(콜백함수)

타입스크립트 Promise의 콜백 함수는 resolve와 reject 함수를 매개변수로 받는 형태이다.

new Promise<T>((
	resolve: (successValue: T) => void,
    reject: (any) => void
) => {
    // 코드 구현
})


resolve와 reject 함수

프로미스 구현 예시

import {readFile} from 'fs';

export const readFilePromise = (filename: string): Promise<string> =>
	new Promise<string>((
    	resolve: (value: string) => void,
        reject: (error: Error) => void
    ) => {
        readFile(filename, (err: Error, buffer: Buffer) => {
            if(err) reject(err);
            else resolve(buffer.toString());
        })
    })

프로미스 사용 예시

readFilePromise('./package.json')
	.then((content: string) => {
    console.log(content);
    return readFilePromise('./tsconfig.json');
	})
	.then((content: string) => {
    console.log(content);
	})
	.catch((err: Error) => console.log('error: ', err.message));
	.finally(() => console.log('프로그램 종료'));


Promise.resolve 메서드

Promise.resolve('hello')
	.then(value => console.log(value)); // hello

Promise.reject 메서드

Promise.reject(new Error('에러'))
	.catch((err: Error) => console.log('error', err.message)); // error: 에러


Promise.all 메서드

Promise.all은 Array 메서드의 every처럼 동작한다.

Promise.all(프로미스 객체 배열: Promise[]): Promise<resolve된 값들의 배열 혹은 any>

Promise.all 메서드는 Promise 객체들을 배열로 받아, resolve된 값들의 배열로 만들어 준다.

reject 객체 발생 시 더 기다리지 않고 reject 객체를 반환한다.

const getResult = <T>(promises: Promise<T>[]) => Promise.all(promises);

getResult<any>([Promise.resolve(true), Promise.resolve('hello')])
	.then(result => console.log(result)); // [true, 'hello']

getResult<any>([Promise.resolve(true), Promise.reject(new Error('에러'))])
	.then(result => console.log(result)) // 호출되지 않는다
	.catch(err => console.log('Error: ', err.message)); // Error: 에러


Promise.race 메서드

Promise.race는 Array 메서드의 some처럼 동작한다.

Promise.race는 Promise 객체를 배열로 받아 하나라도 resolve되면 즉시, resolve 객체를 반환한다.

reject 값이 먼저 발생하면 reject 객체를 반환한다.

Promise.race(프로미스 객체 배열: Promise[]): Promise(가장 먼저 해소된 객체 타입 혹은 Error)

Promise.race([Promise.resolve(true), Promise.reject(new Error('에러'))])
	.then(value => console.log(value)) // true
	.catch(err => console.log(err.message)); // 호출되지 않는다



참고 자료


Do it 타입스크립트 프로그래밍

Typescript-Handbook

HTTP 버전별 차이

|

[펌] HTTP 버전별 차이


요약 :

  1. HTTP/0.9 - ‘One-line Protocol’ 한 줄로 요청이 가능, 간단한 HTML 파일 내용 전달
  2. HTTP/1.0 - 통신응답결과(200,404 등), 헤더와 바디로 분리된 메타 데이터, Content-Type으로 HTML 외에 다른 문서 전송 가능, 버전 정보 등 자세한 정보를 함께 담기 시작
  3. HTTP/1.1 - 첫 번째 표준 프로토콜, 커넥션 재사용, 캐시 제어 가능, 파이프라이닝 추가로 응답이 전송되기 전 후속 요청이 가능, Chunk된 응답 지원으로 분할 응답이 가능, 언어나 인코딩, 타입 협상이 가능하여 서버와 클라 간의 교환이 쉬워짐, Host 헤더 덕분에, 동일 IP 주소에 다른 도메인 호스팅 가능
  4. HTTP/2 - 바이너리 형태의 이진 프로토콜로 이전의 텍스트 프로토콜보다 성능 향상, 스트림(stream)으로 한 번의 커넥션으로 여러 개의 데이터를 주고 받음, 중복되는 헤더를 압축



참고 자료


MDN - HTTP의 진화

HTTP의 버전 별 차이에 대해 알아보고 Ubuntu-Nginx에 HTTP/2를 적용해 봅니다.

Evolution of HTTP — HTTP/0.9, HTTP/1.0, HTTP/1.1, Keep-Alive, Upgrade, and HTTPS

create-react-app env 환경변수 설정

|

create-react-app env 환경변수 설정


create-react-app에는 dotenv라는 모듈이 설치되어 있어서 .env 파일을 생성하여 환경변수 설정이 가능하다.


env 파일 종류

  1. .env - 기본
  2. .env.local - Test를 제외한 모든 환경
  3. .env.development - 개발 환경
  4. .env.test - 테스트 환경
  5. .env.production: 운영 환경


env 설정 시 주의사항

  1. .env 파일을 최상위 root 폴더에 위치시킬 것

  2. REACT_APP_변수명의 filename convention을 지킬 것

  3. 값에 따옴표를 붙이지 말 것

REACT_APP_API_KEY='qfqi321kqlwdn'; // 따옴표를 붙이면 아스키값 그대로 읽는다.
REACT_APP_API_KEY=qfqi321kqlwdn;
  1. 사용시 process.env.REACT_APP_변수명으로 사용할 수 있다
let url = `https://www.example.com/?client_id=${process.env.REACT_APP_API_KEY}`;
  1. 환경변수 설정 완료 후 반드시 서버를 재시작 할 것



참고 자료


Adding Custom Environment Variables

CRA에서 환경 변수 설정하기

리액트 - Create react app (CRA)에서 .env 를 이용한 환경변수 설정 (캐시 버스터, 배포파일 관리)

2021-06-04 TIL - 짤해 프로젝트 완성

|

2021-06-04 TIL


  • 오늘 한 것

    1. 짤해 프로젝트 짤주머니 삭제 기능, 버튼들 기능 전부 구현, 배포 - 짤 위에 버튼을 얹고 싶었지만 antd의 태그를 div로 감싸는 순간 css가 무너지고 말았다. 이쁘진 않더라도 그 옆에 그냥 배치하기로 했다. 삭제 버튼을 누르면 짤 주머니에서 짤이 삭제되는데 먼저, firebase에서 만든 유니크한 key값이 필요했다. 처음에 db에서 데이터를 받아 뿌려줄때 이 key값도 같이 저장을 해놓고 버튼이 눌린 해당 element의 onClick 이벤트에 이 key값을 넣어 db에서 삭제하는데 성공했다. 그러고 화면에서도 이미지가 삭제되어야하는데 남아있는 이미지들의 key값과 삭제된 key값이 같지 않은 녀석들을 filter로 걸러내어 구현했다. 기능들은 얼추 만들어서 배포를 하기 전에 env 환경변수부터 설정했다. 지금까지 dev.js에 const 값으로 넣어서 사용했는데 process.env로 환경변수를 꺼내는 식으로 바꾸었다. (처음부터 그렇게 할껄..) 문제는 여기서부터 시작이었다. 배포 후 망가진 기능들을 보수하는데 거의 시간을 다 보냈다. http 환경에서 작업한 탓에 https와 같이 사용할 수 없었다. cloudinary에 인증되지 않은 사용자로 업로드를 해서 그런가하고 자료를 찾아봤지만 잘 나오지 않고 심지어 공식문서도 형편없었다. 여기서 한 가지 꼼수를 부려서 http://https://// 만 쓰면 환경에 맞춰서 알아서 프로토콜이 지정되는 성질(?)을 이용하기로 했다. 이미지를 호스팅 서버에 올리면 무조건 http:// 로 반환되는데 substring으로 그냥 슬래시 전까지 짤라버렸다. 그렇게 하고 다시 배포를 하니 잘만 돌아간다.
    2. 리액트 tutorial 프로젝트 - infinite scroll 토이 프로젝트를 후딱 만들고 이를 짤해 프로젝트에 적용하기로 했다. 방식은 intersectionObserver도 아니고 react-virtualized도 아닌 scroll event 형식이었지만… 일단 앱이 돌아가게끔 만드는게 최우선이었다.

  • 내일 할 것
    1. 짤해 프로젝트 - 최종점검
    2. 리액트 tutorial 프로젝트
    3. 포트폴리오 제작



  • 끝으로

급하게 포트폴리오를 만들어야할 일이 생겼다. 취준하려면 어차피 만들어야하는거 이 기회에 제대로 만들어놓아야겠다.

오늘의 한 줄 총평 : 무시무시한 CORS


react image download 구현

|

react image download 구현


리액트로 짤방 검색기 프로젝트를 진행하면서 google custom search API로 검색한 짤 이미지를 다운로드하는 기능을 넣어야했다. 몇일 동안의 삽질과 수많은 시도 끝에 구현은 성공했다.

우선은 실패 사례부터 보자면

  1. a 태그에 download attribute를 추가하여 다운로드하는 방법
  2. DOM 자체를 image로 변환하여 다운로드하는 방법
  3. 이미지를 canvas에 옮겨 그리고 base64 형태로 변환하여 다운로드하는 방법
  4. 이미지의 crossOrigin을 변경하여 canvas에 그리고 blob 형태로 변환하여 다운로드하는 방법

기억나는건 이정도인데 이 외에도 다양한 시도를 했던것 같다.


일반적으로 a 태그에 HTML5에서 지원하는 download attribute를 추가하면 된다고 하지만 내 프로젝트 같은 경우에는 이미지 링크가 외부 경로여서 이미지를 다운로드 하는 순간 그냥 브라우저에 이미지가 열리고 끝이 났었다.

코드는 다음과 같다.

const el = document.createElement('a');
el.href = url;
el.download = 'example.png';
el.click();

a 태그를 생성하고 href를 달아 다운로드할 파일명을 추가한 뒤 강제로 a태그를 클릭하여 다운로드 하는 방식이다.

실패


다음은 domtoimage 라이브러리를 사용하여 DOM 자체를 image(blob)로 변환하여 다운로드하는 방법이다.

다운로드는 file-saver 라이브러리의 saveAs 메소드를 사용하였다.

domtoimage
  .toBlob(document.querySelector('#id'))
  .then((blob) => {
    saveAs(blob, 'example.png');
   })
  .catch((err) => {
    console.log(err);
   });

img 태그가 아닌 다른 곳에서 사용하면 정상적으로 이미지가 다운로드되지만 외부경로가 달린 img 태그에서 사용하면 공포의 CORS 이슈가 터지고 말았다. 서버가 있다면 서버에서 이미지를 다운로드 받고 그 이미지를 클라이언트에서 다운로드 받으면 되지만 firebase를 이용한 serverless 프로젝트여서 CORS를 피해갈 순 없었다.

실패


html 태그를 canvas로 옮겨주는 html2canvas 라이브러리를 이용하여 이미지를 base64 형태로 변화 후 다운로드

window.scrollTo(0, 0);
let url = '';
await html2canvas(document.getElementById('id')).then(
  async (canvas) => {
    url = await canvas.toDataURL('image/jpg').split(',')[1];
  }
);

이 방법도 역시 CORS 이슈 때문인지 변환한 이미지 데이터가 그냥 흰색뿐이었다.

실패


CORS 이슈를 피해가려면 이미지 자체에 crossOrigin attribute를 anonymous로 설정하면 된다고 한다.

let img = new Image();
img.crossOrigin = 'Anonymous';
img.src = url;
let canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
canvas.toBlob((blob) => {
  let link = document.createElement('a');
  link.download = 'example.png';
  link.href = URL.createObjectURL(blob);
  link.click();
  URL.revokeObjectURL(link.href);
}, 'image/png');

새 이미지를 만든 후 그 이미지를 이용한 캔버스를 그려 blob으로 다운로드 받는 방법이다.

CORS 이슈는 피해갈 수 있었지만 이미지가 출력되지 않고 빈 화면뿐이었다.

실패


서버가 없다면 서버의 역할을 대신할 이미지 호스팅 업체에 먼저 이미지를 올린 후 그곳의 이미지를 다운로드 받으면 되지 않을까? 라는 생각이 들었다.

이를 위해서 cloudinary라는 이미지 호스팅 서비스를 이용하기로 했다.

먼저 이미지 url로 form data 생성 후 cloudinary에 업로드를 실행한다.

업로드를 하게 되면 cloudinary에 올라간 이미지의 url을 반환받을 수 있는데, 이 때 그 url로 responseType을 blob으로 설정하여 다시 request를 요청한다.

반환받은 결과로 이미지 다운로드를 진행한다.

반신반의로 시작했지만 어라? 되네

const data = new FormData();
data.append('file', url);
data.append('upload_preset', UPLOAD_PRESET);
data.append('cloud_name', CLOUD_NAME);
fetch(`https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload`, {
  method: 'post',
  body: data,
})
  .then((resp) => resp.json())
  .then((temp) => {
    axios
      .get(temp.url, {
        responseType: 'blob',
      })
      .then((res) => saveAs(res.data, 'zzal.png'))
      .catch((err) => console.log(err));
  })
  .catch((err) => console.log(err));

한 번 우회를 한 작업이기 때문에 성능상으로는 떨어지지만 아무튼 성공해서 다행이다.

성공😊


cloudinary 용량 제한이 있어서 이 곳에 이미지를 올리고 다운로드 후에 해당 이미지를 지워야하지만 client-side의 delete는 보안상의 이슈로 구현이 까다롭다.

해당 부분은 추후 찾아봐야겠다.



참고 자료


How to Upload Images to Cloudinary With a React App

axios blob request

Deleting client-side uploaded assets