깊은바다거북
개발 공부 기록
깊은바다거북
전체 방문자
오늘
어제
  • 분류 전체보기 (219)
    • JAVA (9)
    • JavaScript (15)
    • 스파르타코딩클럽 (11)
      • [내일배움단] 웹개발 종합반 개발일지 (5)
      • [내일배움캠프] 프로젝트와 트러블 슈팅 (6)
    • SQL | NoSQL (4)
    • CS 등등 (0)
    • TIL | WIL (173)
    • 기타 에러 해결 (3)
    • 내 살 길 궁리 (4)

인기 글

최근 글

최근 댓글

태그

  • BFS(너비우선탐색)
  • TIT (Today I Troubleshot)
  • 팀 프로젝트
  • Inorder Traversal(중위 순회)
  • 01. 미니 프로젝트
  • 트러블 슈팅 Troubleshooting
  • 자료 구조
  • 자바스크립트 기초 문법
  • 시간 복잡도
  • POST / GET 요청
  • Binary Tree(이진 트리)
  • tree
  • Leetcode
  • 코딩테스트 연습문제
  • BST(이진 탐색 트리)
  • 재귀 함수
  • leetcode-cli
  • Trie
  • Linked List
  • 최소 힙(Min Heap)
  • Til
  • 자잘한 에러 해결
  • DFS(깊이우선탐색)
  • 최대 힙(Max Heap)
  • 점화식(Recurrence Relation)
  • 프로그래머스
  • Backtracking(백트래킹)
  • Preorder Traversal(전위 순회)
  • TypeScript
  • 혼자 공부하는 자바스크립트
hELLO · Designed By 정상우.
깊은바다거북

개발 공부 기록

TIL | WIL

1/19 목 (fetch()는 왜 onload 이벤트보다도 늦는 걸까) TIL, TIT

2023. 1. 19. 23:04

(TypeScript 공부 중)

어제는 강의자료로 주어진 ‘포켓몬 도감’ 코드에서

  • 카드 뒷면에 해당 포켓몬의 다른 이미지 보여주기
  • 포켓몬의 여러 다른 이미지 버전 중 선택할 수 있도록 배열로 만들어 놓음
  • 원래 뒤죽박죽으로 나오던 것을 포켓몬 ID 1번부터 차례로 나열되도록 바꿈

과 같은 수정을 하였다.

오늘은 “왜 asycn/await fetch()로 문서 요소를 동적으로 추가하는 것이 완료되기 전에 document.querySelector 등이 호출되어 버리는 걸까”를 고민하다가 Core Web Vitals와 웹 페이지 성능, 로딩 프로세서 우선순위 조정하기 같은 프론트 개념들만 잔뜩 접하게 되었다. (타입스크립트 미안…)

심지어 DOMContentLoaded와 load 이벤트 리스너까지 걸어주었는데도 동적 요소가 다 만들어지기 전에 document.querySelectorAll을 호출하고 개수가 부족한 배열을 반환하고 끝내버린다. 실마리를 타고 타고 동적 렌더링과 puppeteer까지 왔는데 실험은 아직이다.

(에러 템플릿 추가)


에러 TimeoutError: Waiting failed: 50000ms exceeded - ✔️

발생 상황: puppeteer로 브라우저 페이지를 열어 내 로컬 파일에 접속하려고 한다.

// 크로미움으로 브라우저를 연다.
const browser = await puppeteer.launch();

// 페이지 열기
const page = await browser.newPage();

// 링크 이동
// await page.goto("http://127.0.0.1:5555");
const pokemonDictPathFull = "file:\\\\C:\\Users\\USER\\Desktop\\Sparta\\03.1_typescript_practice\\Pokemon\\06_index.html"
await page.goto(pokemonDictPathFull);

// .card 엘리먼트중에 값이 #100인 .card--id 엘리먼트가 생길때까지 기다림
await page.waitForFunction(
  () =>
    document.querySelector(".card:last-child .card--id").textContent ===
    "#100",
  { timeout: 5000 }
);

에러 메세지 전문:

TimeoutError: Waiting failed: 50000ms exceeded
    at Timeout.<anonymous> (C:\Users\USER\Desktop\Sparta\03.1_typescript_practice\node_modules\puppeteer-core\lib\cjs\puppeteer\common\WaitTask.js:64:32)
    at listOnTimeout (node:internal/timers:564:17)
    at process.processTimers (node:internal/timers:507:7)

TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined
    at Object.writeFile (node:fs:2168:5)
    at C:\Users\USER\Desktop\Sparta\03.1_typescript_practice\Pokemon_nodeJs_to_typescript\07_pokemon_app.js:74:8 {
  code: 'ERR_INVALID_ARG_TYPE'
}

원인: await page.goto()에 들어가는 URI 경로 부분에서 에러가 난다. ⇒ 아니다. page.waitForFunction 부분에서 타임아웃 에러가 난 것이다.

시도: await page.goto()에 들어가는 URI 경로를 여러가지로 바꿔 시도해보았다.

const pokemonDictURL3 = "http://localhost:63342" // 실패
const pokemonDictURL2 = "http://localhost:63342/03.1_typescript_practice/Pokemon/06_index.html?_ijt=66ccaeonhhf2cofel504p57d5m" // 
const pokemonDictURL1 = "http://localhost:63342/03.1_typescript_practice/Pokemon/06_index.html?_ijt=h96cj73p98j09apg88e0nt10m4&_ij_reload=RELOAD_ON_SAVE"
  • 시도 1: pokemonDictURL1, 2 ⇒ “Page “http://localhost:63342/…” requested without authorization” 메세지와 함께 실패
  • 시도 2: pokemonDictURL3 ⇒ 이것은 인식을 못할 줄 알았다. 예상대로 실패.
  • 시도 3: 그리고 검색해서 알게 된 방법인 ‘file://{파일_절대경로}’도 써보았지만 똑같이 인식을 못한다.
    • 왜 안되는지 모르겠다. 다른 사람들은 이 방법으로 잘만 됐다던데… https://stackoverflow.com/questions/47587352/opening-local-html-file-using-puppeteer
  • 시도 4: page.goto() 대신 page.setContent() 사용 ⇒ 실패
  • 시도 5: 아예 유효하지 않은 URL이라면 ProtocolError가 난다.

(1/20 금 TIL에서 계속)

에러 ProtocolError: Protocol error (Page.navigate): Cannot navigate to invalid URL - (우회 해결)

발생 상황: page.goto()에 유효하지 않은 url (localhost:63342 부분)을 인수로 제공함.

const pokemonDictPathFull = "file://localhost:63342/C:\\Users\\USER\\Desktop\\Sparta\\03.1_typescript_practice\\Pokemon\\06_index.html"
const pageMoved = await page.goto(pokemonDictPathFull);

에러 메세지 전문:

ProtocolError: Protocol error (Page.navigate): Cannot navigate to invalid URL
    at C:\Users\USER\Desktop\Sparta\03.1_typescript_practice\node_modules\puppeteer-core\lib\cjs\puppeteer\common\Connection.js:329:24
    at new Promise (<anonymous>)
    at Frame.goto (C:\Users\USER\Desktop\Sparta\03.1_typescript_practice\node_modules\puppeteer-core\lib\cjs\puppeteer\common\Frame.js:207:13)
    at CDPPage.goto (C:\Users\USER\Desktop\Sparta\03.1_typescript_practice\node_modules\puppeteer-core\lib\cjs\puppeteer\common\Page.js:439:91)
    at scrape (C:\Users\USER\Desktop\Sparta\03.1_typescript_practice\Pokemon_nodeJs_to_typescript\07_pokemon_app.js:35:34)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

원인:

시도:

해결: :63342부분을 빼고 url을 주면 일단 이 에러는 잠잠해진다.

⇒ 로컬 파일에서 불러오는 방법은 냅두고 일단 서버를 켜서 인터넷으로 접근하는 방법을 사용하여 해결함.

에러 ReferenceError: document is not defined ✔️

발생 상황:

const pokemonDictURLTest = "https://pokeapi.co/api/v2/pokemon"
const pageMoved = await page.goto(pokemonDictURLTest);
console.log("3. after goto(): ", pageMoved) // HTTPResponse {}
console.log("3. page url: ", page.url()) // https://pokeapi.co/api/v2/pokemon
console.log(document.querySelector(".card:first-child .card--id").textContent)

에러 메세지 전문:

ReferenceError: document is not defined
    at scrape (C:\Users\USER\Desktop\Sparta\03.1_typescript_practice\Pokemon_nodeJs_to_typescript\07_pokemon_app.js:39:17)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

원인: Node.js는 브라우저 환경이 아니고, document 변수가 소속된 DOM은 웹 브라우저에서 쓰이는 것이므로 그냥 백엔드 app.js파일에서 저렇게 쓰면 document 변수를 찾을 수 없는 게 당연한 것이었다.

힌트 조각모음:

  • Next JS 같이 서버 사이드 렌더링을 사용하면 브라우저가 없으므로 window나 document 변수 또한 없다. ⇒ 동적 렌더링(dynamic rendering)으로 바꿔줘야 함
    • Next JS ⇒ dynamic() 메소드 사용
    • React ⇒ useEffect() 메소드 사용
    • 다른 서버사이드 렌더링 라이브러리들 ⇒ componentDidMount 사용?
  • DOM 이 완성되지 않은 상태더라도 document 글로벌 변수는 항상 존재한다고 함. 이 말에 따르면 document/window.addEventListner(’DOMContentLoaded’/’load’, …)로 document를 찍어봐도 … 아니다.
  • puppeteer가 바로 동적 렌더리을 지원한다고 한다..!

시도:

  • 시도 1: page.evaluate() 사용 ⇒ 실패.
    await page.evaluate(() => {
    	console.log(document)
    });
    // => ReferenceError: document is not defined

해결: 1/20일자 TIL에서 해결함.

→ puppeteer를 사용하면 일단 headless ‘브라우저’를 그리게 되므로 document 변수 등이 존재하게 됨.

→ 페이지를 다 그리도록 기다렸다가 document 등을 불러와 쓸 수 있도록 도와주는 page.evaluate() 같은 메소드를 사용하면 document가 undefined 등이 되지 않을 수 있음.

⇒ 거기에 더해 처음 puppeteer.launch로 browser를 만들 때 옵션을 설정해주면 headless 브라우저에서만 존재하던 console.log 등을 내 백엔드 터미널로 가져올 수 있게 됨.

const browser = await puppeteer.launch({
  dumpio: true // 이 설정이 없으면 page.on('console')을 등록해도 작동하지 않는다. 
})

IntelliJ IDE에서 live-server 사용하기 초간단 버전

출처: https://developer-kade.tistory.com/136

npm 명령어로,

npm install -g live-server

이렇게 글로벌 설치를 해주고 아무 프로젝트 폴더로나 들어가서 다시

live-server

라고 입력해주기만 하면 끝!

  • 루트 디렉토리에서 live-server 명령어 실행시 모습 :
  • 브라우저에서 localhost:8080으로 접속한 모습

DOMContentLoaded와 onload 이벤트보다도 늦는 fetch() ? (스압주의)

이것 외에는 저 두 이벤트 리스너 안에 찍은 document.querySelectorAll() 에 100개가 들어와야 하는 것이 아예 들어오지 않거나 1개, 5개씩 들쭉날쭉하게 들어오는 이유를 설명할 방법이 없다.

후….

브라우저의 동작 순서

  1. HTML을 읽기 시작한다.
  1. HTML을 파싱한다.
  1. DOM 트리를 생성한다.
  1. Render 트리(DOM tree + CSS의 CSSOM 트리 결합)가 생성되고
  1. Display에 표시한다

중에 onload 이벤트는 4번 이후에 격발되는 걸로 알고 있는데, 그렇다면 fetch()가 이보다도 늦어진다는 얘기가 된다.

// 06_pokemon_crawling.ts
...
const getPokemon = async (id: number): Promise<void> => {
  const data: Response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
  const pokemon: any = await data.json();
	...
}

바로 요 부분에서 fetch()가 CSS

Fetch 조사중…. fetch 예제:

// fetch() 예시:
fetch('http://example.com/movies.json')
  .then((response) => response.json())
  .then((data) => console.log('성공:', data));
	.catch((error) => console.error('실패:', error));

크롬 개발자 도구의 “우선순위 힌트(Priority Hints)”란:

바로 이 부분을 말한다.

브라우저가 이미지, 스크립트, CSS 같은 리소스를 검색하고 다운로드할 때 이를 최적의 순서로 다운로드하기 위해 각 리소스에 부여하는 ‘우선순위’가 있다. 이렇게 우선순위를 찾은 순서에 따라(=동일한 우선순위라면 발견한 순서대로) 리소스들이 다운로드 된 것을 시각적으로 보여주는 게 바로 저 ‘폭포(Cascade, Priority Hints)’ 파트인 것이다.

이를 통해 어느 리소스가 가장 먼저 다운로드 되었는지 살펴볼 수 있다.

Core Web Vitals

: 구글에서 제공하는 웹페이지 성능 측정 ‘기준’.

  • Largest Contentful Paint(최대 콘텐츠풀 페인트, LCP): 로딩 성능을 측정합니다. 우수한 사용자 경험을 제공하려면 페이지가 처음으로 로딩된 후 2.5초 이내에 LCP가 발생해야 합니다.
    • ⇒ 페이지의 메인 콘텐츠가 로드된 시점을 식별하고 싶어서
    • 가장 큰 요소가 렌더링된 시기를 측정한다.

      = 페이지가 처음으로 로드를 시작한 시점 ~ 뷰포트 내에 있는 가장 큰 이미지 또는 텍스트 블록이 렌더링된 시점

  • First Input Delay(최초 입력 지연, FID): 상호 작용을 측정합니다. 우수한 사용자 경험을 제공하려면 페이지의 FID가 100밀리초 이하여야 합니다.
  • Cumulative Layout Shift(누적 레이아웃 시프트, CLS): 시각적 안정성을 측정합니다. 우수한 사용자 경험을 제공하려면 페이지에서 0.1 이하의 CLS를 유지해야 합니다.

페이지 로드의 75번째 백분위수가 위의 세 가지 메트릭 모두에서 권장 목표를 충족하면 Core Web Vitals 기준을 통과한다고 본다.

Core Web Vitals를 측정하는 방법에는 JavaScript에서 web-vitals 패키지를 사용하여 직접 작성하는 것과 이를 이용한 크롬 확장 프로그램 Web Vitals를 이용하는 방법 등이 있다.

더 자세한 내용은 아래 링크 참조:

Web Vitals
Updated New: Check out Web Vitals Patterns for implementations of common UX patterns optimized for Core Web Vitals. Optimizing for quality of user experience is key to the long-term success of any site on the web. Whether you're a business owner, marketer, or developer, Web Vitals can help you quantify the experience of your site and identify opportunities to improve.
https://web.dev/vitals/#%EA%B0%9C%EC%9A%94

<link rel="preload">를 사용하여 로딩 프로세서의 속도를 높이는 방법

예시:

내가 개발자로서 저 Pacifico 폰트 파일이 중요한 리소스이고 로딩이 오래 걸림을 알고 있다면 얘를 브라우저가 발견할 때까지 기다리지 않고 미리 로딩하고 있으라고 명령하고 싶을 수 있다.

구현 방법:

<head>
  <link rel="preload" as="script" href="critical.js">
</head>
  • as 속성에 허용되는 값은 script, style, font, image 등이다.
  • as 속성을 생략하거나 유효하지 않은 값을 갖는 것은 XHR 요청과 같다고 한다. 여기서 브라우저는 무엇을 가져오는지 알지 못하므로 올바른 우선순위를 판단할 수 없고, 스크립트와 같은 일부 리소스를 두 번 가져올 수도 있게 된다.
  • 그 밖에 폰트나 CSS 파일을 preloading 할 때 주의점들이 있다.
  • 브라우저는 사전 로드된 파일을 실행하지는 않는다. (스크립트를 ‘실행’하거나 스타일시트를 ‘적용’하지 않는다.)

    ⇒ 내가 원하는 건 스크립트 안의 fetch() 메소드를 더 일찍 ‘실행’시키는 것이므로 이 preload 기법은 무용하다.

  • preload는 브라우저에게 강제성을 부여하므로, 최대한 적게 사용하는 것이 좋다. → 사용되지 않은 프리로드는 load 이벤트 후 약 3초 후에 Chrome에서 콘솔 경고를 트리거한다.

출처: https://web.dev/preload-critical-assets/#javascript-파일-사전-로딩

rel=prefetch 이건 뭘까?

fetch()를 미리 시작하게 만들어 주는 것 같지는 않다. 그냥 HTML 페이지를 미리 가져온다는 의미에서 prefetch라고 하는 듯.

우선순위 힌트로 리소스 로딩 최적화하기

우선순위 힌트로 리소스 로딩 최적화하기
원문: https://web.dev/priority-hints/ 저자: Leena Sohoni, Addy Osmani ( Twitter, Github), Patrick Meenan ( Twitter) 라이선스: CC BY 4.0 브라우저가 웹 페이지를 파싱하고 이미지, 스크립트 또는 CSS 같은 리소스를 검색하고 다운로드하기 시작하면 최적의 순서로 리소스를 다운로드하기 위해 각 리소스에 가져오기 우선순위를 할당한다. 이런 우선순위는 리소스의 종류와 문서의 위치에 따라 달라질 수 있다.
https://ui.toast.com/weekly-pick/ko_2021117#%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84-%ED%9E%8C%ED%8A%B8%EB%A1%9C-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EB%A1%9C%EB%94%A9-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0

이곳을 읽고 있다. 다시 읽는다면 웹 페이지 로딩 최적화에 대해 많은 기법과 개념을 배울 수 있을 것이다. 내가 원하는 “fetch()를 더 빨리 완료하거나 마지막 fetch()가 다 될 때까지 기다렸다가 document.addEventListener(”load”, …)등을 실행시키기” 같은 방법을 알려주지는 않는 것 같아 일단 지나간다.

Event Delegation

동적으로 추가된 element에 접근하기 위해서는 event delegation(이벤트 위임)이라는 걸 해야한다고 한다.

예를 들면 원하는 (동적으로 추가될) element에 어떤 클래스 속성을 붙여두고 그 클래스명을 가진 element가 클릭됐을 때에만 이벤트가 실행되도록 한다:

document.body.addEventListener('click', func);

const func = (e) => {
  if (e.target.className === 'example') {
    console.log('element accessed!'};
}

참고중: https://stackoverflow.com/questions/64854551/find-element-on-the-page-after-fetch-js

검색 키워드: “access to dom element after last fetch() method complete”

⇒ puppeteer가 동적 렌더링을 지원해줘서, ‘브라우저(페이지) 컨텍스트에서’ 쿼리셀렉터나 함수를 실행해준다… 는 부분까지 접근했다. 아직 실험은 안 해봤고, 미심쩍은 부분들이 남아있다.

puppeteer

: 구글에서 만든 노드 라이브러리. Headless Chrome 또는 Chrominum을 제어할 수 있다.

(여기 보는 중)

https://github.com/puppeteer/puppeteer/issues/1944
pupeteer ReferenceError: document is not defined - Google Search
Search instead for pupeteer ReferenceError: document is not defined
https://www.google.com/search?q=pupeteer+ReferenceError%3A+document+is+not+defined&newwindow=1&rlz=1C1JZAP_koKR946KR946&ei=ir7IY9-vC9viwAOhgbS4DQ&ved=0ahUKEwjfnKDr3dL8AhVbMXAKHaEADdcQ4dUDCA8&uact=5&oq=pupeteer+ReferenceError%3A+document+is+not+defined&gs_lcp=Cgxnd3Mtd2l6LXNlcnAQAzIHCAAQgAQQDTIFCAAQhgMyBQgAEIYDMgUIABCGAzIFCAAQhgM6CggAEEcQ1gQQsAM6BggAEAcQHjoNCAAQCBAHEB4Q8QQQCkoECEEYAEoECEYYAFCFrT9Y5Mk_YI7MP2gEcAF4AIABpgGIAdwJkgEDMC45mAEAoAEByAEIwAEB&sclient=gws-wiz-serp


Uploaded by N2T

    'TIL | WIL' 카테고리의 다른 글
    • 1/25 수 (CORS 정책, 생각보다 쉬웠다) TIL, TIT
    • 1/20 금 (fetch()는 일단 매우 느리고 타입스크립트는 생각보다 똑똑하다) TIL, TIT
    • 1/18 수 (TypeScript로 간단 포켓몬 도감 만들기) TIL, TIT
    • 1/17 화 (웹 서비스에서 고가용성이란) TIL
    깊은바다거북
    깊은바다거북

    티스토리툴바