210821 "Zone.js has detected that ZoneAwarePromise (window|global).Promise has been overwritten

디버깅 결과 페이지

이번 포스팅에서는 현재 서비스 중인 Angular web app이 iOS 모바일 단말기에서 정상적으로 동작하지 않는 문제에 대해서 작성해보려고 한다.

[Issue]

브라우저에서 디버깅해본 결과, 위의 ERROR를 확인할 수 있었다.
현재 Promise polyfill과 zone.js가 로드되는 과정에서 문제가 되고 있다. 프로젝트 내에서는 zone.js가 로드된 다음에 Promise polyfill가 로드되고 있지만, 이 문제를 해결하기 위해서는 Promise polyfill이 로드된 다음에 zone.js가 로드되어야 한다.
그 이유는 Zone.js의 ZoneAwarePromise가 다음에 로드된 Promise polyfill에 의해 Overwritten되기 때문이다.

[Solution]

현재 프론트엔드 프로젝트의 src/polyfills.ts의 코드를 보면, import 'zone.js/dist/zone'; statement를 확인 할 수 있다. Promise polyfill 다음으로 zone.js를 로드하기 위해서는 앞서 언급한 statement를 main.js로 이전해서 작성해줘야 한다.

(위의 Solution을 적용한 뒤에 정상적으로 동작을 하는지 확인이 필요)

[Promise polyfills과 zone.js의 역할]

Promise polyfills

비동기 코드의 장점을 살리면서 콜백지옥(callback hell)을 피하기 위해 ES6 표준 spec에 포함되어있는 Promise 패턴을 사용한다. 이 Promise polyfill을 사용해서 Promise가 지원되지 않는 브라우저에서 사용될 수 있도록 해야한다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

zone.js(Zones)

Zones는 Angular의 Change Detection을 위해 중요한 역할을 한다. Angular에서는 click, change, input, submit과 같은 사용자에 의한 이벤트XMLHttpRequest를 통한 원격 서버로부터의 데이터 취득, setTimeout과 setInterval을 통해 앱의 상태를 변화시키고 있다.
Angular에서는 화면을 업데이트하는데 위와 같은 비동기 처리를 사용하고, 로직이 작성되어있는 ts 파일의 변수가 HTML Template에 바인딩되어 ts파일내의 변수의 값이 업데이트되었을때 Template내의 값도 같이 업데이트된다.

이처럼 별도로 프레임워크에 변화가 일어났다는 작업을 하지 않아도 Angular는 알아서 처리를 해주게 되는데, 이때 중요한 역할을 해주는 것이 바로 Zones이다.
Angular의 Zones의 onTurnDone 이벤트가 발생할때마다 전체 어플리케이션에 대해 Change Detection을 수행하게 되고, onTurnDone 이벤트는 Angular의 NgZone이 발생을 시킨다.

ref. Zones를 이용하면, 해당 Zones(JavaScript Execution context)가 실행되는 시점에서 여러 이벤트들을 hooking할 수 있다.

(자바스크립트의 실행 영역을 직접 나누는 것을 도와주기 때문에 여러가지 이벤트들을 hooking하는 것이 가능)

이러한 변화감지는 Zones 덕분에 가능한 것이다.

이처럼 Zones는 Angular가 DOM을 언제 업데이트해야 하는지 쉽게 발견할 수 있게 도와주는 역할을 한다.

210509 JavaScript engine과 Web API 그리고 Callback queue


자바스크립트의 비동기 코드가 runtime에 의존적이라고 하는 이유에 대한 의문을 시작으로 이번 포스팅을 시작하려고 한다. 이전에 비동기 함수를 테스팅하는 과정에서 에러가 발생을 했었는데 이때 비동기 코드가 runtime에 의존적인 결과코드이기 때문에 별도의 설정을 해줘야 했고, 이 에러를 겪고 해결해나가기 전에는 그 이유에 대해서 잘 몰랐었기 때문에 혹시 과거의 나와 같이 의문을 갖는 사람들을 위해 포스팅을 해보려고 한다.

이 이유에 대해서 이해하려면 자바스크립트 코드의 실행과정에 대해서 우선적으로 이해를 해야한다.

  • 자바스크립트 코드의 실행과정

자바스크립트에서는 작성된 코드들은 call stack에 쌓인 후에 실행이 된다. 하지만 JS엔진에서는 비동기 작업을 지원하지 않기 때문에 코드들 중에서 비동기 작업을 Web API(browser에 의해 제공)에 위임을 한다.

  • JS runtime engine의 역할

    JS runtime engine은 standard JS 코드를 실행하는 역할을 하는 JS engine(V8 for chrome)로 아래와 같은 프로그래밍 언어로써 독립적으로 요구되는 특징들을 지원한다.

    • variable, functions,
    • scoping, scope chaining, execution context, execution scope
  • Web API의 역할

    • Web API는 Browser로부터 제공되는 wrapper로, 표준 JS 프로그래밍 언어의 일부가 아니며, 아래의 기능들을 지원한다.

    • ajax, events(onkeyPress, onBlur와 같은 이벤트들)

    • console.log, window object, DOM을 읽고 쓰기와 같은 기능

    • 비동기 작업(Asynchronous task)지원

    • 표준 JS 언어는 위와같은 기능들을 지원하지 않기 때문에 브라우저는 JS 엔진을 브라우저의 custom wrapper들로 감싸서 웹에서의 여러 조작들이 지원 가능하도록 한다.
      (위와 같은 이유로 browser가 JS의 runtime으로 불린다)

      cf) Node는 JS에게 위와 같은 기능들을 제공하는 server side에서 동작하는 runtime이다.

Read more

210509 ReferenceError regeneratorRuntime is not defined


NodeJS 프로젝트를 진행하면서 비동기로 작성한 함수를 위한 테스트 코드를 작성하고 실행을 했는데 ReferenceError: regeneratorRuntime is not defined 에러가 발생했다.
이전에 ReactJS 프로젝트를 진행하면서 테스트 코드를 작성했을때도 위와 똑같은 에러가 발생했었는데 이러한 경우에는 아래의 두 방법으로 해결을 할 수 있다.

Solution 1) regenerator-runtime를 설치해서 비동기 함수를 작성한 파일의 최상단에 import 'regenerator-runtime/runtime';를 선언해주면 간단하게 해결할 수 있다.

Solution 2) .babelrc 의 설정을 아래와 같이 업데이트한다.

Using @babel/preset-env without that target set will result in the tests using async failing with ReferenceError: regeneratorRuntime is not defined.

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}

이 regenerator-runtime이 무엇인가 한 번 구글링을 해보니,
regenerator-runtime은 compiled/transpiled 비동기 함수들을 위한 runtime을 지원해주는 녀석이라고 한다.
Babel과 같은 컴파일러는 자바스크립트 syntax에 대해서 최신 문법을 이전 문법으로 변환을 해주지만, runtime에 의존적인 결과 코드(비동기 코드)들은 regenerator-runtime으로부터 지원을 받는다고 한다.

210505 screen.getBy*와 getBy의 차이, findBy*/getBy*/queryBy* method의 차이


테스트 코드를 작성하던 도중에 헷갈렸던 개념들에 대해서 반복학습을 위해 포스팅을 해두려고 한다.

screen.getBy vs getBy

screen을 사용하는 이점은 필요한 query를 추가할 때 더 이상 render 함수로 컴포넌트를 렌더링해서 최신 상태를 유지 하지 않아도 된다는 점이다.
beforeEach()함수 내에서 공통적으로 컴포넌트를 렌더링했다면, 이하의 test 블럭에서는 screen을 통해 렌더링된 컴포넌트를 통해 테스트 코드를 작성할 수 있다.

findBy* / getBy* / queryBy* method의 차이

  • getBy*

    조건에 맞는 요소를 찾았을때 query에 일치하는 노드를 반환
    조건에 맞는 요소를 못 찾았을때 에러를 반환
    → 존재하지 않는 DOM element를 assert할때에는 try-catch문으로 발생한 에러를 별도의 함수에 담아 tobedefined() matcher로 확인을 해야 한다. 이 경우에는 queryBy*를 사용해서 assert하는 것이 좋다.

  • queryBy*

    조건에 맞는 요소를 찾았을때 query에 일치하는 노드를 반환
    조건에 맞는 요소를 못 찾았을때 null을 반환
    → 존재하지 않는 DOM element를 assert할때 유용하다.

  • findBy*

    조건에 맞는 요소를 찾았을때 resolved Promise 반환
    조건에 맞는 요소를 못 찾았을때 rejected Promise 반환

    기존의 waitForElement()함수는 이제 deprecated되었기 때문에 findBy* 함수를 사용해야 한다.

    1
    2
    3
    4
    5
    6
    test('nowPlaying API method 테스트 - 데이터가 문제없이 화면에 로드되는지 확인', async () => {
    const { findByTestId } = render(<Movie />);

    await findByTestId('loading-text');
    await findByTestId('contents-list');
    });

210504 Error) You should not use Link outside a <Router>


<StaticRouter>와 <MemoryRouter>를 사용

이번에 컴포넌트 단위로 단위테스트를 하던 중에 You should not use XXX and <withRouter> outside a <Router>에러를 접했다.
Router의 내부에서만 사용할 수 있는 <withRouter>와 <Link>를 사용한 컴포넌트를 테스트를 할때에는 개별 컴포넌트만 호출해서 렌더링한 후에 테스트를 진행하기 때문에 실제로는 Router 내에 구현이 되어있다고 하더라도 위와같은 에러가 발생하게 되는 것이다.

위의 에러에 대한 해결책은 테스트 코드에서 <MemoryRouter> 또는 <StaticRouter>를 사용해서 테스트하려는 컴포넌트를 감싸서 렌더링해주면 된다.
Header.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import Header from './Header';
import { MemoryRouter } from 'react-router';



describe('header 컴포넌트 내부의 메인 메뉴관련 테스트 코드', () => {
let header;
beforeEach(() => {
header = render(<MemoryRouter><Header/></MemoryRouter>)
});

describe('메뉴 클릭시 제대로 연결된 페이지가 렌더링되는지 확인하는 테스트 코드', () => {
test('Movie 메뉴 클릭시 Movie page component가 렌더링된다.', () => {
const { getByTestId } = header;
expect(getByTestId('movie-menu')).toBeTruthy();
fireEvent.click(getByTestId('movie-menu'));
expect(screen.findByTestId('movie-page-container')).toBeInTheDocument;
});
......

JavaScript로 코딩테스트 준비하기 - 입출력에 대한 이야기

자바스크립트에서 입력받기

Python으로는 간단하게 input()을 사용해서 키보드의 입력을 받아서 처리할 수 있었다. 하지만 이번에 VSCode에서 JavaScript로 알고리즘 문제를 풀면서 키보드로 받은 입력 값을 처리하려고 했는데 입력 이벤트를 계속 받고는 있지만 입력 이벤트가 끝나지 않았다.
왜 이런지 이해가 되지 않아서 방법을 찾아보던 도중에 해결방법을 찾았다.
바로 입력이 끝났다면 ctrl + D를 눌러서 입력 종료를 알려주는 것이다.

예상하지 못한 JavaScript 입/출력 부분의 문제로 계획하지 않은 입출력 관련된 내용으로 포스팅을 하게 되었다.

JavaScript에서 입출력은 fs(file system) 모듈을 사용한다. fs모듈의 readFileSync() 함수를 사용해서 파일이나 표준 입출력을 입력받게 되는데, 아래 예시 코드에서 0을 입력해주는 이유는 표준입력(stdin: standard input)이 파일 설명자로 0이기 때문이다.

nodejs에서 File system에 관한 공식문서 내용은 아래 링크를 참조하도록 하자.
https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_file_system

따라서 별도의 파일을 읽지 않고 표준 입력을 받는 경우에는 내부에 0이라는 인수를 넘겨준다. 0과 함께 encoding을 명시해줘야 하는데 별도로 명시하지 않고 표준입력의 설명자 0만을 넘겨준 경우에는 toString()함수를 사용해서 별도로 String 타입으로 변환을 해줘야 한다.
(변환을 안해주게 되면 <Buffer 31 30 0a>와 같은 raw buffer가 결과값으로 나온다)

1
2
3
4
5
6
7
8
9
const fs = require('fs');

const inputWithNoEncoding = fs.readFileSync(0).toString().split('\n');

const inputWithEncoding = fs.readFileSync(0, 'utf8').split('\n');

const cvtInputToNumber = fs.readFileSync(0, 'utf8').split('\n');
// ['10', '']와 같은 배열의 형태로 값이 반환되기 때문에 [0]번째 인덱스 값을 가져온다.
console.log(Number(cvtInputToNumber[0]));

참고로 readFileSync()의 내부에 작성해준 /dev/stdin은 백준 알고리즘 문제 풀이에서 입력 예제를 넣고 그 파일을 읽어 실행하게 만들기 위해서 작성해준 것이다.

JavaScript에서 입력받는 방법은 앞서 설명한 fs(File System) 모듈을 사용한 방법과 readline 모듈을 사용한 방법이 있다.
readline 모듈은 process.stdin이나 file stream과 같은 Readable stream에서 line by line으로 데이터를 읽어들이기 위한 interface를 제공한다.

1
2
3
4
5
6
7
8
9
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

rl.on('line', (input) => {
console.log(`received: ${input}`);
});
Read more

210316 JavaScript TIL 5일차/6일차 - ES6 함수의 추가 기능과 배열

Preview

이번 예습 범위는 26장 ES6 함수의 추가 기능과 27장 배열이었다. 이전 예습범위를 공부하면서 자바스크립트가 프로토 타입 기반의 언어라는 부분과 함수형 객체 인스턴스가 어떻게 생성이 되는지에 대해 좀 자세히 공부를 했었는데, 그 덕분에 여러가지로 수월하게 26장의 내용을 이해할 수 있었던 것 같다. 이번 기회로 프로토 타입과 관련한 내용을 개인적으로 찾아가며 학습할 수 있었던 좋은 기회였던 것 같다.
콜백함수와 고차 함수의 개념, 프로토 타입 메서드, 정적 메서드 등의 개념을 이해하고 27장을 보니 이전에는 단편적으로만 보였던 메서드들이 다양하게 구분되어 보이기 시작했다. 역시 아는만큼 보이는 것 같다.
아직 개념적으로 공부해야 될 부분이 많기 때문에 지금 느끼는 재미로 좀 더 확장성 있게 공부하도록 해야겠다.

정적 메서드, 프로토타입 메서드, 인스턴스 메서드

Read more

210316 JavaScript TIL 4일차 - 함수, 함수의 정의, 함수 객체 생성, 프로토 타입, 프로토 타입 멤버 속성 추가/삭제, 객체의 동결, 즉시 실행함수, 중첩 함수, 콜백 함수, 순수 함수, 스코프, 전역변수 사용하지 말기, 일단 const로 변수 선언하기

Preview

오늘은 내일 있을 자바스크립트 수업을 위해서 12장 함수 ~ 15장 let, const와 블록 레벨 스코프에 해당하는 교재의 내용을 읽어보고 개인적으로 궁금한 내용들을 찾아보며 공부해보았다.

우선 강사님이 함수 부분이 가장 중요하다고 하셔서 이 부분을 읽을때 좀 더 집중해서 읽어보았다.
다시 한 번 읽어보고 싶은 내용에 대해서 다시 한 번 정리를 해두었다.

Review

이번 수업에서 가장 중요한 개념은 중첩함수와 콜백함수의 개념이였다.

함수(fuction)

  • 자바스크립트에서 함수는 객체이다.

    자바스크립트의 함수객체는 다른 일반 객체와는 다르다. 일반 객체는 호출할 수 없지만, 함수 객체는 호출할 수 있다.

    자바스크립트에서 함수는 객체이기 때문에 함수 객체만이 가질 수 있는 고유한 속성(property)를 가지고 있다.
    아래 첨부한 노트의 첫 번째를 참고하자. 첫 번째 노트의 우측 상단부를 보면, 함수 객체가 가지는 고유속성 5가지(arguments, caller, length, name, prototype)가 있는 것을 알 수 있다.

    순차적으로 언급한 순서로 함수 객체의 고유 속성에 대해서 알아보도록 하자.

    • arguments : 함수의 인수로 iterable한 유사배열 객체이다. 함수 내부에서 사용되는 지역변수이며, 가변 인자((넘겨받을 인수의 갯수가 정해지지 않았을때 함수 내부에서 사용될 수 있다)

      과거에 가변 인자의 사용으로 인해 argument라는 속성을 사용했지만, ES6에서 rest parameter의 개념이 도입되었다. (…args)

    • caller : ECMAScript 사양에 포함되지 않은 비표준 property이다.

    • length : 함수로 전달된 매개변수(parameter)의 갯수

    • prototype : proto type object에 접근이 가능하게 해준다.


  • 함수는 객체 타입의 값이다.

Read more

210315 Rubber Duck Team activity 준비

본 포스팅은 월, 수, 금 아침 시간을 활용한 팀원들과의 activity를 위하여 정리한 개발과 관련된 용어입니다.

암묵적 타입 변환 vs 명시적 타입 변환

객체 리터럴

객체 리터럴에 의한 객체 생성

Object 생성자 함수

생성자 함수

Read more

210314 JavaScript TIL 2일차/3일차 - 원시타입과 객체 타입, 값/참조에 의한 전달, 유사배열 객체 String

원시타입과 객체 타입

원시타입 변수의 경우 값을 재정의 하는 경우, 기존에 사용하던 메모리 위치에 다시 덮어쓰지 않고, 다시 새로운 메모리 위치에 새로운 값을 저장한다.

반면 객체 타입 변수의 경우, 객체를 저장하고 있는 공간의 참조값(주소)을 저장하고 있는 공간과 객체의 값을 저장하고 있는 공간, 두 공간을 갖는다.
따라서 객체 값을 저장하고 있는 변수의 값을 변경하는 경우, 메모리상에 저장되어있는 객체의 값을 수정하게 되는 것이다.


  • 값에 의한 전달

    만약 변수에 원시 값을 갖는 변수를 할당하는 경우, 할당되는 변수에 원시 값이 복사되어 전달된다. (다른 메모리 주소 공간에 복사)


Read more