F/E 기초

인터넷, 웹 사이트, 웹 프로그래밍

  1. 인터넷
    전 세계에 걸쳐 원거리 접속이나 파일 전송, 전자 메일 등의 데이터 통신 서비스를 받을 수 있는 컴퓨터 네트워크의 시스템
  2. 웹 사이트
    도메인, IP 주소, URL을 통하여 접속할 수 있는 웹 페이지들의 의미있는 묶음.
  3. 웹 프로그래밍
    웹 사이트에 접속했을 때 화면의 구성요소를 만드는 작업.
    1. 프론트엔드
      브라우저에서 동작하는 코드를 작성한다. 클라이언트 측 프로그래밍이라고도 한다(client side programming)
    2. 백엔드
      서버에서 실행되는 코드를 작성한다. server side programming.

Browser, Window, Dom, jQuery, Vue

  1. Browser
    1. 크롬 브라우저의 엔진이 html 문서를 렌더링 한다.
    2. <html /> <script />만 해석할 수 있다.
    3. 브라우저 탭에는 실행 환경이 있고 각 탭의 코드는 이 실행환경 안에서 완전히 독립적으로 실행된다. 이는 보안 절차이다.
  2. Window
    1. 브라우저 창 1개를 의미한다.
    2. 개발자는 여기에 직접 접근하여 함수를 작성한다.
    3. 자식 객체로 document, self, navigator, screen, forms, history, loaction ...
    4. 웹 페이지에서 전역 객체는 window 이므로, windows.variable 구문을 통해 전역 변수를 설정하고 접근할 수 있다. window나 frame에서 다른 window나 frame에 선언된 전역 변수에 접근할 수 있다. 예를 들어, phoneNumber 라는 변수가 문서에 선언된 경우, iframe에서 parent.phoneNumber로 이 변수를 참조할 수 있다.
  3. Dom
    1. 브라우저에 담긴 Document를 의미한다.
    2.  각 웹페이지가 로딩될 때 Document 인스턴스가 만들어진다. 전체 웹 페이지 구조와 컨텐츠 그리고 URL같은 기능들을 제공하는 document 가 호출된다.
    3. window.document.querySelector("#id값").textContent = "abc"; 처럼 html의 요소나 속성에 직접 접근하여 값을 제어할 수 있다.
  4. jQuery
    1. $의 선언
    2. $("#id값").innerText("123"); 처럼 보다 쉽게 제어가 가능해졌다.
  5. Vue
    1. 브라우저 엔진이 .vue 파일을 해석할 수 있도록 .html파일로 컴파일하는 프레임워크

브라우저 접속하면 화면이 뜨기까지의 과정을 설명해보세요.

  1. 브라우저는 HTML(네트워크에서 수신한)을 로드한 뒤 구문분석한다.
  2. HTML을 DOM으로 변환(생성)한다. DOM은 컴퓨터 메모리의 문서를 나타낸다. DOM은 CSS와 문서가 만나는 곳이다(아래 이미지 참고).
  3. 브라우저는 HTML 문서에 연결된 임베디드 이미지, 동영상, 링크된 CSS 같은 리소스를 가져온다.
  4. 렌더 트리 : 가져온 CSS를 구문 분석하여 선택자 유형별로 다른 규칙을 각각 다른 '버킷'으로 정렬한다. (예, 요소, 클래스, ID 등 찾은 선택자를 기반으로 DOM의 어느 노드에 어떤 규칙을 적용해야하는지 결정하고, 필요에 따라 스타일을 첨부한다.)
  5. 렌더 트리는 규칙이 적용된 후에 표시되어야 하는 구조로 배치된다.
  6. 페인팅 : 페이지의 시각적 표시하 화면에 표시된다.

https://developer.mozilla.org/ko/docs/Learn/CSS/First_steps/How_CSS_works#css_%EB%8A%94_%EC%8B%A4%EC%A0%9C%EB%A1%9C_%EC%96%B4%EB%96%BB%EA%B2%8C_%EC%9E%91%EB%8F%99%ED%95%A9%EB%8B%88%EA%B9%8C

 


document.ready()와 window.load()

출처 : https://diaryofgreen.tistory.com/96

  • $(document).ready()
    • 브라우저가 돔 트리를 생성한 직후에 실행된다.
    • window.load보다 빠르게 실행되고 중복 사용해도 선언한 순서대로 실행된다.
  • $(window).load()
    • html의 로딩이 끝난 뒤(화면에 필요한 모든 요소:css, js, image, iframe ...이 웹 브라우저의 메모리에 모두 올려진 뒤)에 실행된다.
    • 중복될 경우 하나만 적용되므로 주의가 필요하다.

브라우저에 JSON을 전송할 수 있는 MessageConverter

출처 : https://victorydntmd.tistory.com/172

HTTP 프로토콜에서 메시지는 문자열을 통해 전송된다. 그러나 통신에 문자열이 아닌 JSON을 사용하는 HTTP 통신도 있다. (즉, 전체 페이지를 불러오지 않고도 필요한 부분만을 업데이트 할 수 있다.)
대부분의 브라우저에는 XMLHttpRequest(XHR)는 내장 브라우저가 존재한다. 이는 화면이 없는 브라우저이기 때문에 HTML 문서가 필요 없고, 필요한 데이터를 XML 형식으로 주고 받는 Ajax 통신에 사용됐다. 그런데 어차피 브라우저에서 XML을 컨트롤하는 게 JS이기에, XML로 전달한 뒤 다시 JS로 파싱할 필요가 없음을 깨닫게 됐다. 그래서 JS 객체 형식으로 전달하는 JSON(javascript object notation)을 사용하게 된다.

 

XHR에 대해서도 간단히

var requestURL = "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
var request = new XMLHttpRequest();
request.open("GET", requestURL);
request.responseType = "json"; // XMLHttpRequest 객체가 서버로부터 받은 데이터를 JSON 형식으로 처리하도록 지정하여 응답 데이터를 자동으로 JavaScript 객체로 파싱한다.
request.send();

request.onload = function () { // 이벤트 핸들러로 사용된 익명함수 // 요청을 성공적으로 완료하고 응답을 받았을 때 실행되는 콜백 함수
  var superHeroes = request.response;
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};


addEventListener("load", (event) => { 여기에 이벤트 핸들러 코드 작성 });
- "load" 이벤트에 대한 이벤트 핸들러를 화살표 함수로 정의하고 있다.
- event는 이벤트 객체를 나타내며, 함수 내에서 이벤트 처리 코드를 작성할 수 있다.

onload = (event) => { 여기에 이벤트 핸들러 코드 작성 };

* * 이벤트 핸들러 : 이벤트가 객체에 발생할 때에 작동하는 것


Json 문자열과 Json 객체

출처 : https://6developer.com/5

문자열에서 네이티브 객체로 변환하는 것은 파싱(Parsing).

네트워크를 통해 전달할 수 있게 객체를 문자열로 변환하는 과정은 문자열화(Stringification)

var jsonStr = "{\"name\":\"피곤한 개발자 피로그래머\",\"url\":\"http://niphyang.tistory.com\"}"
var jsonObj = {"name":"피곤한 개발자 피로그래머","url":"http://niphyang.tistory.com"};

 - JSON 문자열을 객체로
var jsonStr = "{\"name\":\"피곤한 개발자 피로그래머\",\"url\":\"http://niphyang.tistory.com\"}"; //JSON 문자열 
var jsonObj = JSON.parse(jsonStr); //객체로 변환

 - JSON 객체를 문자열로
var jsonObj = {"name":"피곤한 개발자 피로그래머","url":"http://niphyang.tistory.com"}; //JSON 객체 
var jsonStr = JSON.stringify(jsonObj); //문자열로 변환

* JSON은 문자열과 프로퍼티의 이름 작성시 큰 따옴표만을 사용해야 한다. 작은 따옴표 불가.


JSON 객체 실습 예제

https://seo-0-dev.tistory.com/38


json 데이터 만드는 방법 _ (1) json object (2) serialize (3) json 문자열

출처 : https://offbyone.tistory.com/196

  1. json object
    var data = { name:"홍길동", email:"kdhong@domain.com" };
  2. serialize
    var data = $('#FormId').serialize();
  3. json 문자열
    var data = JSON.stringify({name:"홍길동",email:"kdhong@domain.com"});

응용

var sendData = {data: JSON.stringify({name:"홍길동",email:"kdhong@domain.com"})};


javascript - json

json이란 / 상속 / this / 생성자 / 프로토타입

https://tistory-pencilcase.tistory.com/220

this : https://tistory-pencilcase.tistory.com/226

현재 객체를 참조하려면 this 키워드를 사용하라. 일반적으로 this는 메서드의 호출 객체를 참조한다. 다음과 같이, this를 점이나 대괄호 표기법과 함께 사용.

구문

this["propertyName"];
this.propertyName;

예제

function validate(obj, lowval, hival) {
  if (obj.value < lowval || obj.value > hival) 
     { console.log("잘못된 값!"); }
}
 
// 아래와 같이 this를 사용하여 함수 사용이 가능하다
<p>18과 99 사이의 수를 입력:</p>
<input type="text" name="age" size="3" onchange="validate(this, 18, 99);" />

 

<메서드>

json 객체 알아내는 메서드 : https://tistory-pencilcase.tistory.com/224

typeof (a); 괄호는 선택사항.

var myFun = new Function("5 + 2");
var shape = "round thing";
var size = 1;
var foo = ["Apple", "Mango", "Orange"];
var today = new Date();
 
typeof myFun; // "function" 반환
typeof shape; // "string" 반환
typeof size; // "number" 반환
typeof foo; // "object" 반환
typeof today; // "object" 반환
typeof dontExist; // "undefined" 반환
 
typeof true; // "boolean" 반환
typeof null; // "object" 반환
 
typeof Date; // "function" 반환
typeof Function; // "function" 반환
typeof Math; // "object" 반환
typeof Option; // "function" 반환
typeof String; // "function" 반환

메서드 : https://tistory-pencilcase.tistory.com/225

구문

propNameOrNumber in objectName;

propNameOrNumber는 속성이나 배열 인덱스를 나타내는 문자열, 숫자, 심볼 표현식이다.

var trees = ["redwood", "bay", "cedar", "oak", "maple"];
0 in trees; // true
10 in trees; // false
"bay" in trees; // false
"length" in trees; // true (length는 Array의 속성이므로)
 
// 내장 객체
"PI" in Math; // true
var myString = new String("str");
"length" in myString; // true
 
// 사용자 정의 객체
var mycar = { make: "Honda", model: "Accord", year: 1998 };
"make" in mycar; // true 반환

json & 배열을 다루는 메서드 : https://tistory-pencilcase.tistory.com/223

delete / splice


Javascript 실습

https://programmingsummaries.tistory.com/92

 


javascript의 목적은 뭘까요?

javascript의 목적은 DOM API를 통해 HTML과 CSS를 동적으로 수정하여 사용자 인터페이스를 업데이트하는 것이다.

javascript API

클라이언트 측 javaScript 언어 위에 구축된 기능을 API라고 부른다. application programming interface.

이 API는 두 개의 범주로 나뉜다.

  • 브라우저 API
  • 서드파티 API

브라우저 API는 웹 브라우저에 내장되어있다.

  • Dom (document object model) API를 사용하여 HTML과 CSS를 동적으로 조작할 수 있다. 새로운 팝업창 띄우기, 새로운 콘텐츠가 표시될 때마다 DOM이 작동하는 예를 들 수 있다.
  • Geolocation API로 지리정보를 가져올 수 있다. 구글지도가 사용자의 위치를 찾아 지도에 표시하는 것처럼.
  • Canvas와 WebGL API를 사용하여 2D, 3D 그래픽을 만들 수 있다.
  • HTMLMediaElement와 WebRTC를 포함하는 오디오와 비디오 API를 사용하여 웹 페이지에서 바로 오디오 및 비디오를 재생하거나 웹 카메라에서 비디오를 가져와 다른 사람의 컴퓨터에 표시하는 작업을 할 수 있다.

인터프리터와 컴파일러

인터프리터를 사용하는 언어에서는 코드가 위에서 아래로 실행되고 코드 실행 결과가 즉시 반환된다. 컴파일 언어와 달리, 브라우저에서 코드를 실행하기 전에 코드를 다른 형태로 변환할 필요가 없다. 코드는 인간에게 친숙한 텍스트 모양으로 수신되어 바로 처리된다.

컴파일러를 사용하는 컴파일 언어는 컴퓨터에서 실행되기 전에 다른 형태로 변환(컴파일)된다. C/C++에서는 코드를 기계언어로 변환하여 그 결과를 컴퓨터가 실행한다. 프로그램은 프로그램의 원본 소스 코드에서 생성된 이진 형식(바이너리)로부터 실행된다.

 

인터프리터를 사용하는 프로그래밍 언어인 Javascript

웹 브라우저는 Javascript 코드를 원문 텍스트 형식으로 입력받아 실행한다. 자세히 알아본다면, 대부분의 javascript 인터프리터들은 JIT 컴파일이라는 기술을 사용한다. (just in time 컴파일) 스크립트의 실행과 동시에 소스코드를 더 빠르게 실행할 수 있는 이진 형태로 변환하여 속도를 올리는 방법이다. 하지만 컴파일이 '미리 처리되는 것'이 아니고, 런타임에 처리되므로 javascript는 인터프리터 언어로 분류된다.

 

서버 사이드이면서 클라이언트 사이드 코드인 Javascript

클라이언트 사이드 코드는 사용자의 컴퓨터에서 실행되는 코드다. 웹 페이지에서 페이지의 클라이언트 측 코드가 다운로드 된 후 실행되어 브라우저에 표시된다.

서버 사이드 코드는 서버에서 실행되고 그 결과가 다운로드되어 브라우저에 표시된다. PHP, Python, Ruby, ASP.NET, Javascript가 있다. js는 브라우저 뿐만 아니라 Node.js 환경처럼 서버 사이드 언어로도 사용할 수 있다.


스크립트를 적절한 시점에 불러오기

HTML 코드보다 js가 먼저 불러와진다면 코드가 제대로 동작하지 못한다. 이를 방지하기 위하여 HTML 본문 전체를 불러와 읽었다는 것을 나타내는 브라우저의 DOMContentLoaded이벤트를 수신하는 이벤트 수신기를 사용한다.

document.addEventListener("DOMContentLoaded", () => {
  // …
});

또는 script 태그에 기능을 추가할 수 있다.

<script src="script.js" defer></script>

이는 <script> 태그 요소에 도달하면 브라우저에 HTML 콘텐츠를 계속 다운로드하도록 지시하는 defer 속성을 활요한 것이다. 이 경우 스크립트와 HTML이 동시에 로드되어 코드가 동작한다.

고전적인 방법으로는 body 태그의 뒤에 script 태그를 붙일 수도 있다. 그러나 HTML DOM을 모두 불러오기 전에는 스크립트의 로딩과 분석이 중단되기 때문에 대형 사이트의 경우 성능 저하 문제가 발생한다.

스크립트 중단 문제를 해결하는 데에는 두가지 방법이 있다.

  • async
  • defer

async 특성을 지정하면 스크립트를 가져오는 동안 페이지 로딩을 중단하지 않는다. 그러나 다운로드가 끝나면 스크립트가 바로 실행되며 실행 도중에는 페이지 렌더링이 중단된다. (스크립트와 페이지 렌더링이 동시 진행될 수 없다) 스크립트의 실행 순서를 보장할 방법이 없기에 async는 스크립트가 서로 독립적으로 실행되고 다른 스크립트에 의존하지 않는 경우에 사용하는 것이 좋다. 또 로드할 백그라운드 스크립트가 많고 가능한 한 빨리 제자리에 배치하고 싶을 때 사용하는 것이 좋다.

defer 속성으로 로드된 스크립트는 페이지에 표시되는 순서대로 로드된다. 또한 페이지 콘텐츠를 모두 불러오기 전까지는 실행하지 않으므로 스크립트가 DOM의 위치에 의존하는 경우 유용하다.

정리하자면

  • async와 defer 모두 브라우저가 페이지의 다른 내용(DOM 등등)을 불러오는 동안 스크립트를 별도 스레드에서 불러온다. 덕분에 스크립트를 가져오는 동안 페이지 로딩이 중단되지 않는다.
  • async 속성을 지정한 스크립트는 다운로드가 끝나는 즉시 실행된다. 이렇게 하면 현재 페이지의 렌더링을 중단하며, 특정 실행 순서가 보장되지 않는다.
  • defer 속성을 지정한 스크립트는 순서를 유지한 채로 가져오며 모든 콘텐츠를 다 불러온 이후에 실행한다.
  • 스크립트를 즉시 실행해야 하고 종속성이 없는 경우 async를 사용하는 게 좋다.
  • 다른 스크립트에 의존하거나 DOM 로딩이 필요한 스크립트에는 defer를 사용하여 스크립트를 로드하고, 원하는 순서에 맞춰서 <script> 요소를 배치는 게 좋다.

말 나온김에 짚고 넘어가는 비동기

비동기 프로그래밍은 작업이 완료될 때까지 기다리지 않고 잠재적으로 오래 실행되는 작업을 시작하여 해당 작업이 실행되는 동안에도 다른 이벤트에 응답할 수 있게 하는 기술이다. 대표적인 비동기 기능은 다음과 같다.
- fetch()를 이용한 HTTP 요청
- showOpenFilePicker()를 통해 사용자에게 파일 선택을 요청하기.

비동기를 알기 전에 장기 실행되는 동기 함수의 문제점 먼저 짚고 넘어가자.

const name = "Miriam";
const greeting = `Hello, my name is ${name}!`;
console.log(greeting); // "Hello, my name is Miriam!"

 

간단해보이는 이 세 줄의 코드는 동기 프로그래밍이다. 브라우저는 각 지점에서 다음 줄로 넘어가기 전까지 현재 라인의 작업이 끝나기를 기다리기 때문이다.

function makeGreeting(name) {
  return `Hello, my name is ${name}!`;
}

const name = "Miriam";
const greeting = makeGreeting(name);
console.log(greeting); // "Hello, my name is Miriam!"

 

makeGreeting()은 동기 함수이다. 호출자는 함수의 작업이 완료될 때까지 기다렸다가 값을 반환해야하기 때문이다.

장기 실행 함수의 예로는 소수 100000개를 생성하는 함수같은 것이 있을 것이다. 이것은 결과를 반환하기까지 몇 초의 시간이 걸린다. 그 시간동안 클릭도 할 수 없고 입력도 할 수 없다.
우리가 필요한 건 다음과 같다. 1. 함수를 호출하여 장기적으로 실행되는 작업을 시작한다. 2. 작업을 시작한 뒤 즉시 복귀하여 다른 이벤트에 계속 응답할 수 있게 한다. 3. 첫번째 작업이 완료되면 결과를 알려준다. 이게 바로 비동기 함수가 할 수 있는 일이다.

대표적인 비동기 처리기의 예시로는 이벤트 핸들러가 있다. 이벤트 핸들러는 이벤트가 발생할 때마다 호출되는 함수를 제공한다. 이 '이벤트'가 비동기 작업 완료, 라면 이 구조를 사용하여 호출자에게 비동기 함수 호출 결과를 알릴 수 있다.
초기의 비동기 API는 이런 이벤트 방식을 사용했다. XMLHttpRequest는 Javascript를 사용하여 원격 서버에 HTTP 요청을 할 수 있는 API다. 이것이 이벤트 핸들러와 유사하지만 다른 점이 있다. 그건 바로 이벤트가 버튼 클릭과 같은 사용자 행동이 아닌 어떤 객체의 상태 변화에 의해 발생한다는 것이다.

 

콜백

이벤트 핸들러는 콜백의 특정 유형이고, 콜백은 JavaScript에서 비동기 함수를 구현하는 주요 방식이었다. 그러나...

function doStep1(init) {
  return init + 1;
}

function doStep2(init) {
  return init + 2;
}

function doStep3(init) {
  return init + 3;
}

function doOperation() {
  let result = 0;
  result = doStep1(result);
  result = doStep2(result);
  result = doStep3(result);
  console.log(`result: ${result}`);
}

doOperation(); // 6

여기서는 세 단계로 나뉘는 단일 작업이 있다. 각 단계는 이전 단계에 의존적이다. 이 예제에서 첫 번째 단계는 입력값에 1을 추가하고, 두 번째 단계는 2를 추가하고, 세 번째 단계는 3을 추가한다. 0의 입력부터 시작하여 최종 결과는 6(0 + 1 + 2 + 3)이다. 동기식 프로그램으로서, 이것은 매우 간단하다. 하지만 콜백을 사용하여 단계를 구현하면 어떨까?

function doStep1(init, callback) {
  console.log('1 s')
  const result = init + 1;
  callback(result);
  console.log('1 e')
}

function doStep2(init, callback) {
console.log('2 s')
  const result = init + 2;
  callback(result);
  console.log('2 e')
}

function doStep3(init, callback) {
console.log('3 s')
  const result = init + 3;
  callback(result);
  console.log('3 e')
}

function doOperation() {
  doStep1(0, (result1) => {
    doStep2(result1, (result2) => {
      doStep3(result2, (result3) => {
        console.log(`result: ${result3}`);
      });
    });
  });
}

doOperation();

1 s
2 s
3 s
result: 6
3 e
2 e
1 e

 

이러한 이유로 대부분의 최신 비동기 API는 콜백을 사용하지 않는다. 대신 Promise를 쓴다.


Promise

Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체다.

비동기로 음성 파일을 생성해주는 createAudioFileAsync()라는 함수가 있었다고 생각해보자. 해당 함수는 음성 설정에 대한 정보를 받고, 두 가지 콜백 함수를 받는다. 하나는 음성 파일이 성공적으로 생성되었을때 실행되는 콜백, 그리고 다른 하나는 에러가 발생했을때 실행되는 콜백이다.

function successCallback(result) {
  console.log("Audio file ready at URL: " + result);
}

function failureCallback(error) {
  console.log("Error generating audio file: " + error);
}

createAudioFileAsync(audioSettings, successCallback, failureCallback); // 콜백함수를 '전달'해주는 고전적인 방식

 

…모던한 함수들은 위와 같이 콜백들을 전달하지 않고 콜백을 붙여 사용할 수 있게 Promise를 반환해준다. 만약 createAudioFileAsync() 함수가 Promise를 반환하도록 수정한다면, 다음과 같이 간단하게 사용될 수 있다.

createAudioFileAsync(audioSettings).then(successCallback, failureCallback);

 

…조금 더 간단하게 써보자면:

const promise = createAudioFileAsync(audioSettings); // 이 함수는 Promise를 반환한다.
promise.then(successCallback, failureCallback); // Promise 객체에 콜백을 '붙여' 사용한다.

 

우리는 이와 같은 것을 비동기 함수 호출이라고 부른다. 기본적으로 promise는 함수에 콜백을 전달하는 대신에, 콜백을 첨부하는 방식의 객체이다. 이런 관례는 몇 가지 장점을 갖고 있다.

1.  보장성

콜백 함수를 전달해주는 고전적인 방식과는 달리, Promise는 아래와 같은 특징을 보장한다. 콜백은 자바스크립트 Event Loop가 현재 실행중인 콜 스택을 완료하기 이전에는 절대 호출되지 않는다. 비동기 작업이 성공하거나 실패한 뒤에 then() 을 이용하여 추가한 콜백의 경우에도 위와 같다. then()을 여러번 사용하여 여러개의 콜백을 추가 할 수 있다. 그리고 각각의 콜백은 주어진 순서대로 하나 하나 실행된다.

2. Chaining

보통 두 개 이상의 비동기 작업을 순차적으로 실행해야 하는 상황을 흔히 보게 된다. 순차적으로 각각의 작업이 이전 단계 비동기 작업이 성공하고 나서 그 결과값을 이용하여 다음 비동기 작업을 실행해야 하는 경우를 의미한다. 우리는 이런 상황에서 promise chain을 이용하여 해결하기도 한다. // 이럴거면 동기를 사용하면 되는 거 아니야?

then() 함수는 새로운 promise를 반환한다. 처음에 만들었던 promise와는 다른 새로운 promise이다.

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

 

또는

const promise2 = doSomething().then(successCallback, failureCallback); // doSomething()뿐만 아니라 successCallback or failureCallback 의 완료를 의미한다.

 

두 번째 promise는 doSomething() 뿐만 아니라 successCallback or failureCallback 의 완료를 의미한다. successCallback or failureCallback 또한 promise를 반환하는 비동기 함수일 수도 있다. 이 경우 promise2에 추가된 콜백은 successCallback또는 failureCallback에 의해 반환된 promise 뒤에 대기한다. // 이게 뭔소리지

 

기본적으로, 각각의 promise는 체인 안에서 서로 다른 비동기 단계의 완료를 나타낸다.

예전에는 여러 비동기 작업을 연속적으로 수행하면 고전적인 '지옥의 콜백 피라미드'가 만들어졌다.

// 콜백지옥의 모습
doSomething(function (result) {
  doSomethingElse(
    result,
    function (newResult) {
      doThirdThing(
        newResult,
        function (finalResult) {
          console.log("Got the final result: " + finalResult);
        },
        failureCallback,
      );
    },
    failureCallback,
  );
}, failureCallback);

 

모던한 방식으로 접근한다면, 우리는 콜백 함수들을 '반환된 promise'에 'promise chain을 형성하도록 추가'할 수 있다:

doSomething()
  .then(function (result) {
    return doSomethingElse(result);
  })
  .then(function (newResult) {
    return doThirdThing(newResult);
  })
  .then(function (finalResult) {
    console.log("Got the final result: " + finalResult);
  })
  .catch(failureCallback);

 

then 에 넘겨지는 인자는 선택적(optional)이다. 그리고 catch(failureCallback) 는 then(null, failureCallback) 의 축약이다. 이 표현식을 화살표 함수로 나타내면 다음과 같다.  (화살표 함수 () => x는 () => {return x;}와 같다.)

doSomething()
  .then((result) => doSomethingElse(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(failureCallback);

 

중요: 반환값이 반드시 있어야 함! 만약 없다면 콜백 함수가 이전의 promise의 결과를 받지 못한다.

3. Chaining after a catch

chain에서 작업이 실패한 후에도 새로운 작업을 수행하는 것이 가능하며 매우 유용하다. (예 : catch)

new Promise((resolve, reject) => {
  console.log("Initial");

  resolve();
})
  .then(() => {
    throw new Error("Something failed");

    console.log("Do this");
  })
  .catch(() => {
    console.log("Do that");
  })
  .then(() => {
    console.log("Do this, whatever happened before");
  });

 

참고: "Do this" 텍스트가 출력되지 않은 것을 주의깊게 보자. "Something failed" 에러가 rejection을 발생시켰기 때문이다.

4. Error propagation

'콜백 지옥'에서 failureCallback이 3번 발생한 것을 기억할 것이다. promise chain에서는 단 한 번만 발생하는 것과 비교된다.

doSomething()
  .then((result) => doSomethingElse(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => console.log(`Got the final result: ${finalResult}`))
  .catch(failureCallback);

 

기본적으로 promise chain은 예외가 발생하면 멈추고 chain의 아래에서 catch를 찾는다. 이를 동기 코드로 바꾸면 아래와 같다.

try {
  const result = syncDoSomething();
  const newResult = syncDoSomethingElse(result);
  const finalResult = syncDoThirdThing(newResult);
  console.log(`Got the final result: ${finalResult}`);
} catch (error) {
  failureCallback(error);
}

 

비동기 코드를 사용한 이러한 대칭성은 ECMAScript 2017에서 async/await 구문(Syntactic sugar) 에서 최고로 느낄 수 있다.

async function foo() {
  try {
    const result = await doSomething();
    const newResult = await doSomethingElse(result);
    const finalResult = await doThirdThing(newResult);
    console.log(`Got the final result: ${finalResult}`);
  } catch (error) {
    failureCallback(error);
  }
}

 

Promise는 모든 오류를 잡아내어, 예외 및 프로그래밍 오류가 발생해도 콜백 지옥의 근본적인 결함을 해결한다. 이는 비동기 작업의 기능 구성에 필수적이다. async/await에 대해서는 이쪽에서 계속 https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function

Proomise rejection events

Promise가 reject될 때마다 두 가지 이벤트 중 하나가 전역 범위에 발생한다.(일반적으로, 전역 범위는 window거나, 웹 워커에서 사용되는 경우, Worker, 혹은 워커 기반 인터페이스이다.)

  • rejectionhandled (en-US)
    executor의 reject함수에 의해 reject가 처리 된 후, promise가 reject 될 때 발생한다.
  • unhandledrejection (en-US)
    promise가 reject되었지만 사용할 수 있는 reject 핸들러가 없을 때 발생한다.

PromiseRejectionEvent(https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent)의 유형인 두 이벤트에는 멤버 변수인 promise와 reason 속성이 있다. promise (en-US)는 reject된 promise를 가리키는 속성이고, reason (en-US)은 promise가 reject된 이유를 알려 주는 속성이다.
이들을 이용해 프로미스에 대한 에러 처리를 대체(fallback)하는 것이 가능해지며, 또한 프로미스 관리시 발생하는 이슈들을 디버깅하는 데 도움을 얻을 수 있다. 이 핸들러들은 모든 맥락에서 전역적(global)이기 때문에, 모든 에러는 발생한 지점(source)에 상관없이 동일한 핸들러로 전달된다.

// unhandledrejection 이벤트를 처리하는 핸들러를 추가해서 처리하기.

window.addEventListener(
  "unhandledrejection",
  (event) => {
    /* You might start here by adding code to examine the
     promise specified by event.promise and the reason in
     event.reason */

    event.preventDefault();
  },
  false,
);

 

오래된 콜백 API를 Promise로 만들기

이상적인 프로그래밍 세계에서는 모든 비동기 함수는 promise을 반환해야 하지만, 불행히도 일부 API는 여전히 success 및 / 또는 failure 콜백을 전달하는 방식일거라 생각한다. 예를 들면 setTimeout () 함수가 있다.

setTimeout(() => saySomething("10 seconds passed"), 10000);

 

setTimeout에는 문제가 있다. 함수 saySomething()이 실패하거나 프로그래밍 오류가 있으면 아무 것도 잡아 내지 않는다.
이 때 Promise 생성자를 사용해 문제되는 함수를 감싸고 직접 호출하지 않는 방법으로 우회할 수 있다.

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
wait(10000)
  .then(() => saySomething("10 seconds"))
  .catch(failureCallback);


... composition부터 밑에까지 이해못함
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Using_promises#composition


콜백과 Promise 비동기 처리 코드를 비교해본다.

콜백은 failureCallback()을 여러번 작성해야하고 읽기도 힘들다.

* * chooseToppings(function (a) {}, failureCallback) 의 반복구조

chooseToppings(function (toppings) {
  placeOrder(
    toppings,
    function (order) {
      collectOrder(
        order,
        function (pizza) {
          eatPizza(pizza);
        },
        failureCallback,
      );
    },
    failureCallback,
  );
}, failureCallback);

Promise로 작성할 경우 .catch()하나로 모든 에러 처리가 가능하다. 또한 main thread를 차단하지 않으므로 프로그램은 피자를 주문하고기다리는 동안 마저 게임을 할 수 있을 것이다. 각 함수는 실행되기 전에 이전 작업이 끝낼때까지 기다린다. 이런 방식으로 여러개의 비동기 작업을 연쇄적으로 처리할 수 있다. 왜냐면 각 .then() 블럭은 자신이 속한 블럭의 작업이 끝났을 때의 결과를 새로운 Promise로 반환해주기 때문이다.

* * chooseToppings().then(function (a) {}).then(function (b) {}).then(function (c) {}).catch(failureCallback);

chooseToppings()
  .then(function (toppings) {
    return placeOrder(toppings);
  })
  .then(function (order) {
    return collectOrder(order);
  })
  .then(function (pizza) {
    eatPizza(pizza);
  })
  .catch(failureCallback);

익명함수를 사용했으므로, 화살표 함수를 사용하여 코드를 더욱 간결하게 바꿀 수 있다. () => x 는 () => { return x; } 의 약식 표현이다.

익명함수
var pow = function (a) { reutrn a * a };
const pow = (a) => a * a;

function(response) { return response.blob(); } 는 아래와 같다.
(response) => response.blob();
chooseToppings()
  .then((toppings) => placeOrder(toppings))
  .then((order) => collectOrder(order))
  .then((pizza) => eatPizza(pizza))
  .catch(failureCallback);

함수는 argumenets를 직접 전달하므로 아래와 같이 축약할 수도 있다. 그러나 가독성이 나쁘므로 사용하지 않는 게 낫다.

chooseToppings()
  .then(placeOrder)
  .then(collectOrder)
  .then(eatPizza)
  .catch(failureCallback);

 

 

출처 : https://programmingsummaries.tistory.com/325

promise 패턴을 사용하면 비동기 작업을 순차적으로 진행하거나 병렬로 진행하는 컨트롤이 가능해진다.

// promise 선언부
var _promise = function (param) { // 프로미스 선언
    return new Promise(function (resolve, reject) {
        window.setTimeout(function () { // 비동기 구현을 위해 3초 딜레이 설정
            if (param) {
                resolve("해결완료");
            } else {
                reject(Error("실패"));
            }
        }, 3000);
    });
};

// promise 실행부
_promise(true)
.then(function (text) {
    console.log(text); // 해결완료
    }, function (error) {
    console.error(error);
});

promise는 말 그대로 '약속'이다. "지금은 없으니 나중에 줄게"라는 약속. 정확히는 "지금은 없는데 이상없으면 나중에 주고, 없으면 알려줄게"라는 약속이다.

따라서 promise는 다음 중 하나의 상태(state)가 된다. (Promise는 한번에 성공/실패 중 하나의 결과값을 가진다. 하나의 요청에 두 번 성공하고나 실패할 수 없다. 또한 이미 성공한 작업이 다시 실패로 돌아갈 수 없고 실패한 작업이 성공으로 돌아갈 수 없다.)

  • pending : 아직 수행 중인 상태
  • resolved : Promise 결과가 반환되면 결과에 상관없이 이 상태이다.
  • fulfilled : 약속이 지켜진 상태. 이 상태가 되면 Promise 체인의 다음 .then() 블럭에서 사용할 수 있는 값을 반환한다. 그리고 .then() 블럭 내부의 executor 함수에 Promise에서 반환된 값이 파라미터로 전달된다.
  • rejected : 약속이 못 지켜진 상태. 에러 메세지를 포함한 결과가 반환된다. .catch()에서 확인 할 수 있다.
  • settled : 약속의 성공 여부와 무관하게 일단 결론이 난 상태

위 코드에서 promise의 선언부를 보면 나중에 promise 객체를 생성하기 위해 promise 객체를 리턴하도록 함수로 감싸고 있다. promise 객체만 보면 파라미터로 익명함수를 담고 있고, 익명 함수는 resolve와 reject를 파라미터로 받고 있다.

new Promise로 promise가 생성되는 직후부터 resolve/reject가 호출되기 전까지의 순간을 pending 상태라고 볼 수 있다.

후에 비동기 작업이 끝난 뒤 약속대로 결과물을 줄 수 잇다면 첫번째 파라미터로 주입되는 resolve 함수를 호출하고, 실패했다면 두번째 파라미터로 주입되는 reject함수를 호출하는 것이 promise의 주요 개념이다.

 

_promise()를 호출하면 Promise 객체가 리턴된다. Promise 객체에는 정상적으로 비동기 작업이 완료되었을 때 호출하는 then이라는 API가 존재한다. then API는 첫번째 파라미터에 성공 시 호출할 함수(콜백으로, executor라고 부릅니다)를, 두번째 파라미터에 실패 시 호출할 함수를 선언하면 Promise의 상태에 따라 수행하게 된다.

 

Promise의 반환 객체를 사용하여 이미지를 로드하는 script 예제를 짜본다.

<!DOCTYPE html>
<html lang="en-US">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>My test page</title>
</head>
<body>
<p>This is my page</p>
</body>
<script>
  let promise = fetch("coffee.jpg"); // promise변수에 fetch() 작업으로 반환된 Promise 오브젝트를 저장

  // 1. promise2 = promise.then( 여기에 아래 코드가 들어감. 여기는 fulfilled 상태에만 실행됨. fulfilled 상태가 되면 Response가 반환될 것임. ); // 반환된 response 오브젝트를 매개변수로 전달한다.
  // 2. (response) => response.blob(); // response를 blob 오브젝트로 변환하여 Response Body가 완전히 다운로드 됐는지 확인
  let promise2 = promise.then((response) => response.blob()); // 1, 2를 한 문장으로 줄이면 이런 모양이 됨.

  // blob() 메서드도 성공한다면 Promise를 반환할 것이다. 따라서 두 번째 Promise(promise2)의 .then() 메서드를 호출함으로써 "이행시 반환되는" Blob 오브젝트를 처리할 수 있다(아래 코드가 그 처리 과정이다). 한줄로 끝나지 않는다면 중괄호를 사용하자.
  let promise3 = promise2.then((myBlob) => { // myBlob이 promise2의 결과로 반환된 Blob 오브젝트이다!
    let objectURL = URL.createObjectURL(myBlob); // 오브젝트가 가지고있는 URL이 반환된다.
    let image = document.createElement("img"); // img 엘리먼트 생성
    image.src = objectURL; // 엘리먼트의 src 속성에 저장
    document.body.appendChild(image);
  });
</script>
</html>

이걸 체이닝 형태로 변경하면 아래처럼 수정된다.

  fetch("coffee.jpg") // fetch()의 이행 시 fulfilled라면 반환되는 Promise object를 response라는 매개변수로 전달
   .then((response) => response.blob())
   .then((myBlob) => { // .then()의 이행 시 fulfilled라면 반환되는 Blob object를 myBlob이라는 이름으로 사용
     let objectURL = URL.createObjectURL(myBlob);
     let image = document.createElement("img");
     image.src = objectURL;
     document.body.appendChild(image);
   })
  .catch((e) => {
    console.log( "There has been a problem with your fetch operation: " + e.message, );
  })

fulfilled promise 결과에 의해 반환된 값이 다음 .then() 블록의 executor 함수가 가진 파라미터로 전달 된다는 것을 꼭 기억하면 promise에 대한 이해는 쉬울 것이다.

 

만약 체이닝 형태로 연결된 상태에서 비동기 작업이 중간에 에러가 나면 어떻게 처리해야할까. 그 때를 위해 존재하는 API가 catch API이다. .then(null, function(){})을 메서드 형태로 바꾼거라고 생각해도 좋다.

_promise(true)
.then(JSON.parse)
.catch(function () {
    console.log('체이닝 중간에 에러 발생')
    }).then(function (text) {
    console.log('text')
})

위에서 만든 _promise 객체는 성공 혹은 실패시 JSON 객체가 아닌 string을 리턴하므로 JSON.parse에서 에러가 난다. 따라서 다음 then으로 이동하지 못하고 catch에서 받게 된다. catch는 이와 같이 promise가 연결되어 이을 때 발생하는 오류를 처리해주는 역할을 한다.

`.then()` 및 `.catch()` 블록은 프로미스에서 사용되며, 동기 코드의 `try...catch` 블록의 비동기 버전이라고 볼 수 있다. 알아두어야 할 점은 비동기 코드에서 동기 `try...catch`는 작동하지 않는다는 것이다.

보다 최근의 현대 브라우저에서는 .finally() 메서드를 사용할 수 있다. 이 메서드를 Promise 체이닝의 끝에 배치하여 코드 반복을 줄이고 좀 더 우아하게 일을 처리할 수 있다. 아래와 같이 마지막 블럭에 적용할 수 있다.

myPromise
  .then((response) => {
    doSomething(response);
  })
  .catch((e) => {
    returnError(e);
  })
  .finally(() => {
    runFinalCode();
  });

프로미스(Promises)는 함수의 반환값이나 반환까지 걸리는 시간을 예측할 수 없는 비동기 애플리케이션을 개발하는 데 좋은 방법이다. 프로미스를 사용하면 깊게 중첩된 콜백을 사용하지 않고 비동기 작업의 연속을 표현하고 이해하기가 더 쉬워진다. 또한 동기적인 try...catch 문과 유사한 에러 처리 스타일을 지원한다.


Promise


Promise를 사용한 async와 await

Promise를 반환한다면 promise.all()과 async / await, 프로미스 체이닝 등 프로미스로 할 수 있는 모든 것을 할 수 있다.

먼저 Promise를 사용하지 않은 알람 예제이다.

  function setAlarm() {
    window.setTimeout(() => {
      output.textContent = "Wake up!";
    }, 1000);
  }

  button.addEventListener("click", setAlarm);

Promise를 사용한 예제는 다음과 같다.

  function alarm(person, delay) {
    return new Promise((resolve, reject) => { // promise 생성자
      // executor에는 두개의 인수가 필요하다. resolve, reject
      // executro 구현에서는 비동기 함수를 호출한다.
      // 비동기 함수가 성공하면 resolve를, 실패하면 reject를 호출한다.
      if (delay < 0) {
        throw new Error("Alarm delay must not be negative");
      }
      window.setTimeout(() => {
        resolve(`Wake up, ${person}!`);
      }, delay)
    })
  }

  /* 프로미스 사용 예제 */
  button.addEventListener("click", () => {
    alarm(name.value, delay.value)
      .then((message) => output.textContent = message)
      .catch((error) => output.textContent = `Couldn't set alarm: ${error}`);
  });

  /* async await 사용 예제 */
  button.addEventListener("click", async () => {
    try {
      const message = await alarm(name.value, delay.value);
      output.textContent = message;
    } catch (error) {
      output.textContent = `Couldn't set alarm: ${error}`;
    }
  })

모든 프로미스가 fulfilled일 경우 실행하고 싶다면 Promise.all()

스테틱 메서드인 Promise.all()은 Promise의 배열을 매개변수로 삼고, 배열의 모든 Promise가 fulfil일 때만 새로운 fulfil Promise 오브젝트를 반환한다.

Promise.all([a, b, c]).then(values => {
  ...
});

 

배열의 모든 Promise가 fulfil 이면, .then() 블럭의 executor 함수로의 매개변수로 Promise 결과의 배열을 전달한다. Promise.all() 의 Promise의 배열 중 하나라도 reject라면, 전체 결과가 reject가된다.

 

먼저 각각의 fetch를 수행하는 함수 3개를 작성해본다.

  // 프로미스를 반환하는 함수 a, b, c
  let a = fetch(url1);
  let b = fetch(url2);
  let c = fetch(url3);
  // 위의 Promise가 fulfilled가 됐을 때, fulfilment handler 핸들러로 전달된 values 매개변수에는 각 fetch() 의 결과로 발생한 세 개의 Response 오브젝트가 들어있다.

  // 세 개의 fetch() 작업이 끝난 뒤 다음 요청을 진행할 pormise 코드
  Promise.all([a, b, c]).then(value => {});

이를 promise.all()을 적용하면 아래와 같은 모양이 된다.

function fetchAndDecode(url, type) {
    return fetch(url) // URL에서 리소스를 받아온다.
            .then((response) => { // Promise를 연쇄적으로 호출하여 디코딩된 Response Body를 반환
              // 어떤 종류의 파일을 디코딩해야 하는지에 따라 다른 Promise를 반환
              if (type === "blob") {
                return response.blob();
              } else if (type === "text") {
                return response.text();
              }
            })
            .catch((e) => {
              console.log( "There has been a problem with your fetch operation: " + e.message, );
            })
            .finally(() => {
              console.log(`fetch attempt for "${url}" finished.`);
            });
  }

  let coffee = fetchAndDecode("coffee.jpg", "blob");
  let tea = fetchAndDecode("tea.jpg", "blob");
  let description = fetchAndDecode("description.txt", "text");

  Promise.all([coffee, tea, description]).then((values) => {
    // executor // executor는 세 가지 Promise가 resolve될 때만 실행된다. 그리고 executor가 실행될 때 개별적인 Promise의 결과를 포함하는 [coffee-results, tea-results, description-results] 배열을 매개 변수로 전달받는다.

    console.log(values);

    // Store each value returned from the promises in separate variables; create object URLs from the blobs
    let objectURL1 = URL.createObjectURL(values[0]);
    let objectURL2 = URL.createObjectURL(values[1]);
    let descText = values[2];

    // Display the images in <img> elements
    let image1 = document.createElement("img");
    let image2 = document.createElement("img");
    image1.src = objectURL1;
    image2.src = objectURL2;
    document.body.appendChild(image1);
    document.body.appendChild(image2);

    // Display the text in a paragraph
    let para = document.createElement("p");
    para.textContent = descText;
    document.body.appendChild(para);
  });

콘솔창은 이렇다.

 

 

 

 

 

 

 


Promise() constructor

주로 Promise기반이 아닌 구식 비동기 API코드를 Promise기반 코드로 만들고 싶을 경우 사용한다. 이 방법은 구식 프로젝트 코드, 라이브러리, 혹은 프레임워크를 지금의 Promise 코드와 함께 사용할 때 유용하다.

let timeoutPromise = new Promise((resolve, reject) => { // resolve와 reject는 각각 fulfiled와 reject일 때 실행된다.
  setTimeout(function () {
    resolve("Success!");
  }, 2000);
});

timeoutPromise.then((message) => {
  alert(message); // 2초 뒤에 Success!로 출력된다.
});

 

timeoutPromise.then(alert); 도 같은 기능을 한다. 그러나 권장하지 않는다.


resolve와 reject 메세지를 커스텀하여 지정할 수 있다. Promise가 reject되면 에러는 .catch() 블럭으로 전달된다.

위의 예시를 활용하여 reject()와 fulfil()일 때 다른 메세지를 전달할 수 있게 만들어보자. 또 timeoutPromise() 함수는 Promise를 반환하므로, .then(), .catch(), 기타등등 을 사용해 timeoutPromise에 대하여 Promise 체이닝을 만들 수 있다.

function timeoutPromise(message, interval) {
  return new Promise((resolve, reject) => { //  함수를 실행하면 우리가 사용하고 싶은 Promise가 반환된다.
    // 유효성 검사
    if (message === "" || typeof message !== "string") {
      reject("Message is empty or not a string");
    }
    if (interval < 0 || typeof interval !== "number") {
      reject("Interval is negative or not a number");
    }

    setTimeout(function () {
      resolve(message); // Promise를 resolve한다.
    }, interval)
  })
}

timeoutPromise("Hello", 1000)
.then((message) => {
  alert(message);
})
.catch((e) => {
  console.log("Error: " + e);
})

스레드와 웹 워커

웹 워커를 사용하면 웹 애플리케이션이 작업을 별도의 스레드로 넘길 수 있다. 메인 스레드와 워커는 변수를 직접 공유하지 않고, 상대방이 message 이벤트로 수신하는 메시지로 소통한다. 이는 메인 애플리케이션의 응답성을 유지하는데는 효과적인 방법이다.



프로그램이 장시간의 동기 작업을 수행할 때 창이 완전히 응답하지 않던 것을 기억하는지? 근본적으로 그 이유는 프로그램이 단일 스레드 이기 때문이다. (스레드 : 프로그램이 따르는 일련의 명령) 이 프로그램이 단일 스레드로 구성되어 있어서 한 번에 한 가지 작업만 수행할 수 있었기 때문이다. 따라서 실행 중인 동기 호출이 반환되기를 기다리고 있는 동안엔 다른 작업을 수행할 수 없다.

비동기로 처리하지 않고도 다른 작업을 수행할 수 있는 방법이 있다. 그게 바로 웹 워커다. 워커는 다른 스레드에서 어떤 작업을 실행할 수 있는 기능을 제공한다.

하지만 대가가 있나니, 다중 스레드 코드를 사용하면 스레드가 언제 중단되고 다른 스레드가 언제 실행될지 절대 알 수 없다. 따라서 두 스레드가 같은 변수에 접근할 수 있다. (변수의 값이 언제든지 예기치 않게 변경될 수 있다는 것) 이에 따라 찾기 어려운 버그가 발생한다.

이 문제를 방지하기 위해 메인 코드와 워커 코드가 서로의 변수에 직접 접근할 수 없도록 했다. 워커와 메인 코드는 완전히 별개의 세계에서 실행되며, 서로 메시지를 보내야만 상호 작용할 수 있다. 특히 워커는 DOM(window, document, 페이지 요소 등)에 액세스할 수 없다.

워커에는 세 가지 유형이 있다.

  • dedicated workers
    단일 스크립트 인스턴스에서 사용된다.
  • shared workers
    서로 다른 창에서 실행되는 여러 스크립트에서 공유가 가능하다.
  • service workers
    마치 프록시 서버처럼, 사용자가 오프라인 상태일 때 웹 애플리케이션이 작동할 수 있도록 리소스를 캐싱한다.

웹 워커 사용하기

소수 계산 페이지로 돌아가보자. 우리는 이제 워커를 사용하여 소수 계산을 실행할 것이므로 사용자에 대한 페이지 응답성을 유지할 수 있을 것이다.

 

동기 소수 생성기 코드 : 현재 프로그램에서는 generatePrimes()를 호출한 후 프로그램이 전혀 응답하지 않는다. (아래 링크에서 확인 가능)

https://github.com/waveaway77/simpleHTML/tree/edccde98b1e3fe44a3114787767aa8540bebe0ac/javascript_object/web-worker/as-is

 

이 코드를 고쳐본다. 메인 코드를 main.js에 추가하고 워커 코드를 generate.js에 추가할 것이다. index.html에는 main.js만 포함되어있다. 

main.js code is below

const worker = new Worker("./generate.js"); // Worker() 생성자에 워커 스크립트를 전달한다. 워커 생성 즉시 스크립트가 실행될 것이다.

// 버튼에 이벤트 처리기를 추가한다. 이전과 다르게 함수를 호출하는 대신 워커에게 메세지를 보낸다.
document.querySelector("#generate").addEventListener("click", () => {
    const quota = document.querySelector("#quota").value;
    worker.postMessage({ // 버튼을 누를 경우 워커에게 메세지를 전송한다
        command: "generate",
        quota: quota,
    });
});


// worker가 이벤트가 발생하여 main thread로 메시지를 보낼 때, #output을 업데이트한다.
worker.addEventListener("message", (message) => { // 워커에게 message 이벤트 처리기를 추가
    document.querySelector(
        "#output",
    ).textContent = `Finished generating ${message.data} primes!`; //  처리기는 메시지의 data 속성에서 데이터를 가져와 출력한다.
});

document.querySelector("#reload").addEventListener("click", () => {
    document.querySelector("#user-input").value =
        'Try typing in here immediately after pressing "Generate primes"';
    document.location.reload();
});

generate.js code is below

// 메인 스크립트가 워커를 만들자마자 이 코드를 실행한다

// 워커가 가장 먼저 하는 일은 워커의 전역 함수인 addEventListener()을 사용하여 메인 스레드의 메세지를 listen하는 것이다.
// 메세지의 명령어가 "generate이라면 generatePrimes() 함수를 호출한다.
addEventListener("message", (message) => {
    if (message.data.command === "generate") { // data 속성은 메인 스크립트에서 전달된 인수의 복사본이 들어있다.
        generatePrimes(message.data.quota);
    }
});

function generatePrimes(quota) {
    function isPrime(n) {
        for (let c = 2; c <= Math.sqrt(n); ++c) {
            if (n % c === 0) {
                return false;
            }
        }
        return true;
    }

    const primes = [];
    const maximum = 1000000;

    while (primes.length < quota) {
        const candidate = Math.floor(Math.random() * (maximum + 1));
        if (isPrime(candidate)) {
            primes.push(candidate);
        }
    }

    // 값을 반환하는 대신 작업이 끝나면 워커의 전역 함수인 postMessage()함수를 사용하여 메인 스크립트로 메시지를 보낸다.
    postMessage(primes.length);
    //  메인 스크립트는 이 메시지를 수신하고 있으며 메시지가 수신되면 DOM을 업데이트한다.
}

Javascript의 변수

 

javascript의 선언 방법

  • var : 변수를 선언하는 동시에 값을 초기화. 지역 및 전역 변수를 선언하는데 사용할 수 있다.
  • let : 블록 스코프 지역 변수를 선언하는 동시에 값을 초기화
  • const 상수 : 블록 스코프 읽기 전용 상수 선언, 인터페이스의 특정 부분을 가리키는 참조를 상수에 저장할 수 있다.
  • 템플릿 리터럴 : 백틱을 사용하여 문자열을 선언한다. 다른 변수나 표현식을 포함할 수 있다. 여러 줄로 선언할 수 있으며, javascript를 삽입할 수 있다.

어떤 함수의 바깥에 변수를 선언하면, 현재 문서의 다른 코드에 해당 변수를 사용할 수 있기에 전역 변수라고 한다. 만약 함수 내부에 변수를 선언하면, 오직 그 함수 내에서만 사용할 수 있기에 지역 변수라고 부른다.

ECMAScript 2015 이전의 JavaScript는 블록 문 스코프가 없다.

if (true) {
  var x = 5; // x의 스코프는 전역 맥락이다. x의 스코프는 if문 블록에 제한되지 않는다.
}
console.log(x); // 5

이 동작은 let 선언을 사용하면 바뀐다 (ECMAScript 2015에 도입됨).

if (true) {
  let y = 5;
}
console.log(y); // ReferenceError: y is not defined

변수 할당

지정된 초기 값 없이 var 혹은 let 문을 사용해서 선언된 변수는 undefined 값을 갖습니다. 선언되지 않은 변수에 접근을 시도하는 경우 ReferenceError 예외가 발생합니다.

var a;
console.log(a); // undefined
console.log(b); // undefined // 변수 호이스팅으로 출력됨
var b;
console.log(c); // ReferenceError

let x;
console.log(x); // undefined
console.log(y); // ReferenceError
let y;

undefined - false & NaN

if문에서 검증 가능

var input;
if (input === undefined) { // true

undefined은 false

var myArray = []; // false
if (!myArray[0]) myFunction();

수치맥락에서 NaN

var a;
a + 2; // NaN으로 평가

null - false & 0

var n = null;
console.log(n * 32); // 0

 

* * JavaScript는 대소문자를 구분하므로, null은 Null, NULL과 다르다.

변수 스코프

if (true) { var x = 5; }
console.log(x); // 5

if (true) { let y = 5; // 블록 스코프 지역변수이므로 }
console.log(y); // ReferenceError 발생

 

구조 분해 할당

객체에 키 벨류 따로 안적고 그냥 변수명이 선언되어 있으면 자동으로 변수명을 키로 넣어주고 밸류를 변수에 할당된 데이터로 넣어준다.

2023.10.05 - [┝ framework] - 객체 분해(destructuring) 구문

구조 분해 할당 구문을 사용하여 객체 리터럴에서 값을 풀기 위해 변수를 선언할 수 있다. 예를 들면, let { bar } = foo. 이 구문은 bar라는 이름의 변수를 생성하고 foo 객체에 있는 동일한 이름의 키에 해당하는 값을 변수에 할당한다.

변수 호이스팅

js는 변수 호이스팅으로 인해 나중에 선언된 변수를 참조할 수 있다. 즉 JavaScript 변수가 어떤 의미에서 함수나 문의 최상단으로 "올려지는" (혹은 "끌어올려지는") 것이다.

console.log(x === undefined); // true // 변수 호이스팅으로 가능
var x = 3;

var myvar = "my value";
(function () {
  console.log(myvar); // undefined // 함수 내에서는 정의되지 않았기 때문에 변수 호이스팅으로 최상단으로 호출되어 초기화되기 때문인듯...? 잘 모르겠음
  var myvar = "local value";
})();

ECMAScript 2015의 let과 const는 변수를 블록의 상단으로 끌어올리지만 초기화하지 않는다. 변수가 선언되기 전에 블록 안에서 변수를 참조하게 되면 ReferenceError를 발생시키게 되는데, 변수는 블록 시작부터 선언이 처리될 때까지 "temporal dead zone"에 위치하게 되기 때문이다.

console.log(x); // ReferenceError
let x = 3;

함수 선언은 호이스팅되지만 함수 표현식은 호이스팅 되지 않는다.

// 함수 선언
foo(); // bar
function foo() { console.log("bar"); }

// 함수 표현식
baz(); // TypeError: baz is not a function
var baz = function () { console.log("bar2"); };

상수

상수에 할당된 객체의 속성은 보호되지 않는다.

const MY_OBJECT = { key: "value" };
MY_OBJECT.key = "otherValue"; // 실행가능
const MY_ARRAY = ["HTML", "CSS"];
MY_ARRAY.push("JAVASCRIPT");
console.log(MY_ARRAY); //logs ['HTML','CSS','JAVASCRIPT'];

배열

let fish = [, "Lion", , "Angel", ];
fish[0]; // undefined
fish[1]; // Lion
fish[2]; // undefined
fish[3]; // Angel
fish[4]; // undefined
fish.length; // 4

객체 리터럴

 유효한 식별자가 아닌 속성명은 점(.) 속성으로 접근할 수 없다. 대신 []으로 접근 가능.

var unusualPropertyNames = {
  '': 'blue',
  '!': 'Bang!'
}
console.log(unusualPropertyNames.'');   // SyntaxError: Unexpected string
console.log(unusualPropertyNames.!);    // SyntaxError: Unexpected token !
console.log(unusualPropertyNames['']);  // blue
console.log(unusualPropertyNames['!']); // Bang!

문자열 리터럴

템플릿 리터럴 (ECMAScript 2015)

문자열의 특수문자 사용

\0 Null Byte
\b Backspace
\f Form feed
\n New line
\r Carriage return
\t Tab
\v Vertical tab
\' Apostrophe 혹은 작은 따옴표
\" 큰 따옴표
\\ 백슬래시
\XXX Latin-1 인코딩 문자는 0 - 377 사이 8진수 3자리 XXX까지 지정될 수 있습니다.
예를 들어, \251은 copyright 심볼을 표현하는 8진수 시퀀스입니다.
\xXX Latin-1 인코딩 문자는 00 - FF 사이의 16진수 2자리 XX로 지정될 수 있습니다.
예를 들어, \xA9는 copyright 심볼을 표현하는 16진수 시퀀스입니다.
\uXXXX 유니코드 문자는 16진수 4자리 XXXX로 지정될 수 있습니다.
예를 들어, \u00A9는 copyright 심볼을 표현하는 유니코드 시퀀스입니다. Unicode escape sequences (en-US)를 참고하세요.
\u{XXXXX} 유니코드 코드 포인트 이스케이프.
예를 들어, \u{2F804}는 간단한 유니코드 이스케이프 \uD87E\uDC04와 같습니다.
c:\temp를 문자열에 할당하기 위해선 var home = "c:\\temp";

줄바꿈을 이스케이프한 예시 (전행 백슬래시를 사용해서 백슬래시와 줄바꿈 모두를 없앰)

var str =
  "this string \
is broken \
across multiple \
lines.";
console.log(str); // this string is broken across multiple lines.
var poem = `Roses are red,
Violets are blue.
Sugar is sweet,
and so is foo.`;

poem; // 'Roses are red,\nViolets are blue.\nSugar is sweet,\nand so is foo.'

숫자 맞추기 게임 실습

// HTML
<button>Press me</button>
<div id="greeting"></div>

// JS
const button = document.querySelector("button");

function greet() {
  const name = prompt("What is your name?");
  const greeting = document.querySelector("#greeting");
  greeting.textContent = `Hello ${name}, nice to see you!`;
}

button.addEventListener("click", greet);

 

이벤트 수신기는 특정 이벤트의 발생을 감지, 이벤트 처리기를 호출하며 이벤트 처리기가 바로 이벤트에 반응하는 코드 블록이다.

<input type="number" min="1" max="100" required id="guessField" class="guessField">
<script>
const guessField = document.querySelector(".guessField");
guessSubmit.addEventListener("click", checkGuess);
</script>

위 코드는 guessSubmit 버튼에 이벤트 수신기를 추가한다. 각각 수신할 이벤트 유형(click)을 가리키는 문자열과, 이벤트가 발생하면 실행할 코드(checkGuess() 함수)이다. (checkGuess에 ()를 붙이지 않았음)

 

code is below;

더보기

<body>
<div class="form">
<label for="guessField">Enter a guess: </label>
<input type="number" min="1" max="100" required id="guessField" class="guessField">
<input type="submit" value="Submit guess" class="guessSubmit">
</div>

<div class="resultParas">
<p class="guesses"></p>
<p class="lastResult"></p>
<p class="lowOrHi"></p>
</div>

<script>
let randomNumber = Math.floor(Math.random() * 100) + 1;


const guesses = document.querySelector(".guesses"); // querySelector()는 참조를 가져오고자 하는 요소를 선택할 수 있는 CSS 선택자를 요구한다.
const lastResult = document.querySelector(".lastResult");

const lowOrHi = document.querySelector(".lowOrHi");

const guessSubmit = document.querySelector(".guessSubmit");
const guessField = document.querySelector(".guessField"); // HTML의 텍스트 입력 칸을 가리키는 참조를 저장한다.

let guessCount = 1;

let resetButton;
guessField.focus(); // 페이지 로딩이 끝나면 텍스트 커서가 자동으로 <input> (en-US)에 가도록 한다.

function checkGuess() {

const userGuess = Number(guessField.value); // 텍스트 필드에 입력된 현재 값을 저장한다
if (guessCount === 1) {

guesses.textContent = "Previous guesses: ";
}
guesses.textContent += userGuess + " ";

if (userGuess === randomNumber) {
lastResult.textContent = "Congratulations! You got it right!";
lastResult.style.backgroundColor = "green";
lowOrHi.textContent = "";
setGameOver();
} else if (guessCount === 10) {
lastResult.textContent = "!!!GAME OVER!!!";
lowOrHi.textContent = "";
setGameOver();
} else {
lastResult.textContent = "Wrong!";
lastResult.style.backgroundColor = "red";
if (userGuess < randomNumber) {
lowOrHi.textContent = "Last guess was too low!";
} else if (userGuess > randomNumber) {
lowOrHi.textContent = "Last guess was too high!";
}
}

guessCount++;
guessField.value = "";
guessField.focus();
}

guessSubmit.addEventListener("click", checkGuess);

function setGameOver() {
guessField.disabled = true;
guessSubmit.disabled = true;
resetButton = document.createElement("button");
resetButton.textContent = "Start new game";
document.body.append(resetButton);
resetButton.addEventListener("click", resetGame);
}

function resetGame() {
guessCount = 1;

/* 모든 정보 텍스트 문단의 내용을 지움 */
const resetParas = document.querySelectorAll(".resultParas p"); // <div class="resultParas"></div> 안의 모든 문단 요소를 선택
for (const resetPara of resetParas) { // 하나씩 순회
resetPara.textContent = ""; // 각각의 textContent를 ''(빈 문자열)로 설정
// resetParas는 상수지만, 상수의 내부 속성인 textContent는 바꿀 수 있다.
}


resetButton.parentNode.removeChild(resetButton); // HTML에서 초기화 버튼을 제거

guessField.disabled = false; // 양식 요소를 다시 활성화
guessSubmit.disabled = false; // 양식 요소를 다시 활성화
guessField.value = ""; // 입력 칸을 비운 후
guessField.focus(); // 포커스를 부여해서 새로운 숫자를 입력받을 준비

lastResult.style.backgroundColor = "white";


randomNumber = Math.floor(Math.random() * 100) + 1;
}
</script>
</body>


js 변수

선언한 변수와 선언되지 않은 변수는 출력할 때 다르게 출력된다. myName과 myAge는 값이 할당되지 않은 빈 컨테이너이기 때문에 undefined가 출력된다. 선언되지 않은 변수는 반면 오류가 출력된다.

선언된 변수는 등호를 사용하여 값을 초기화할 수 있다. 변수를 선언하며 동시에 초기화하는 것도 가능하다.

var myName = "Chris";

여러 줄 문자열로 JavaScript 프로그램을 작성할 때(write a multiline JavaScript), 변수를 선언하기 전에 해당 변수의 값을 초기화 할 수 있다. JavaScript 에서 일반적으로 변수 선언문이 다른 코드 보다 먼저 실행되기 때문인데, 이 동작을 호이스팅이라고 한다.

자바스크립트 객체

var dog = { name: "Spot", breed: "Dalmatian" };

dog.name; // Spot

 

JavaScript는 "느슨한 유형의 언어(loosely typed language)"이다. 즉, 다른 언어와 달리 변수에 포함 할 데이터의 유형을 지정할 필요가 없다. 데이터 유형 확인  함수 (typeof myNumber;)


기본적인 메서드는 src/test/java/com/example/playground/TestClassTest.java 참고


js 숫자

10진수는 각 자릿수에 0-9를 사용한다는 의미입니다. 16진수는 1~10, A~F를 사용합니다. (ex: css의 #02798b)

const a = 1.625232532;
a.toFixed(2); // 1.63
let myNumber = "74";
myNumber = Number(myNumber) + 3; // 77

증감연산자는 숫자에 직접 사용이 불가능하다. 변수에 새로운 업데이트된 값을 할당하는 것이기 때문에.

3++; // Uncaught SyntaxError ...
var num1 = 4;
num++; // num1 = num1 + 1

https://iwantadmin.tistory.com/114

var a = 1; var b = 1;
a++; // 1
a; // 2
++b; // 2 // 브라우저가 변수를 먼저 증감하고 값을 반환하므로 2가 출력된다.
b; // 2

++i는 내부적으로 다음과 같이 동작한다.
1. i의 값을 1 더한다.
2. i의 값을 반환한다.

i++는 내부적으로 다음과 같이 동작한다.
1. i의 현재 값을 보관한다. (현재 실행되는 명령문에서는 이 보관된 값이 사용되어야 하니까)
2. i의 값을 1 더한다.
3. 보관했던 값을 반환한다.


js 문자열

const goodQuotes1 = 'She said "I think so!"';
const goodQuotes2 = `She said "I'm not going in there!"`;
const bigmouth = 'I\'ve got no right to take my place…';
const myString = "123";
const myNum = Number(myString);
console.log(typeof myNum); // number

const myNum2 = 123;
const myString2 = String(myNum2);
console.log(typeof myString2); // string
var browserType = "mozilla";

browserType.length; // 7
browserType[0]; // 'm'
browserType[browserType.length - 1]; // 'a'
browserType.indexOf("zilla"); // 2 // 하위문자열 zilla는 상위문자열의 2번위치에서 시작한다
browserType.indexOf("zillaaa"); // -1
if (browserType.indexOf("mozilla") !== -1) { ... } // 하위 문자열 'mozilla'가 포함되지 않은 문자열의 모든 인스턴스를 찾으려면
browserType.slice(0, 3); // 'moz' // (추출을 시작할 인덱스, 추출할 문자의 갯수)
browserType.slice(2); // 'zilla' // 2*에서부터* 자른다

 

var radData = "My NaMe Is MuD";
radData.toLowerCase();
radData.toUpperCase();
browserType.replace("moz", "van"); // vanilla

includes

// html
  <div class="output" style="min-height: 125px;">
    <ul></ul>
  </div>

// script
  const list = document.querySelector('.output ul');
  list.innerHTML = '';
  const greetings = ['Happy Birthday!',
    'Merry Christmas my love',
    'A happy Christmas to all the family',
    'You\'re all I want for Christmas',
    'Get well soon'];

  for (let greeting of greetings) {
    if (greeting.includes('Christmas')) {
      const listItem = document.createElement('li');
      listItem.textContent = greeting;
      list.appendChild(listItem);
    }
  }

js 배열

var random = ["tree", 795, [0, 1, 2]];
random[2][2]; // 2
random.length; // 3
random[2].length // 3

var myData = "Manchester,London,Liverpool,Birmingham,Leeds,Carlisle";
// string to array : split
var myArray = myData.split(",");
myArray // ['Manchester', 'London', 'Liverpool', 'Birmingham', 'Leeds', 'Carlisle']
// array to string : join은 구분자 지정 가능
var myNewString = myArray.join(":");
myNewString; // 'Manchester:London:Liverpool:Birmingham:Leeds:Carlisle'
// array to String : toString은 구분자 지정 불가능
var myNewString = myArray.toString();
myNewString; // 'Manchester,London,Liverpool,Birmingham,Leeds,Carlisle'
myArray.push("Bradford", "Brighton"); // 배열의 새 길이가 리턴된다.
myArray.pop(); // 제거된 원소가 리턴된다.
myArray.upshift("Edinburgh");
myArray.shift();

js 배열

생성 / 채우기 / 반복 / 메서드 / 버퍼와 뷰 / 형식화배열

https://tistory-pencilcase.tistory.com/227


key 기반 컬렉션

https://tistory-pencilcase.tistory.com/227


js 조건문(if, switch, 삼항연산자)과 반복문(for, do while)

어떠한 값들이든 false, undefined, null, 0, NaN이나 빈 문자열('')이 아닌 값은 조건문으로 테스트되었을 때, 실제로는 true를 리턴한다.

let cheese = "Cheddar";
if (cheese) { // true
...

let shoppingDone = false;
if (shoppingDone) { // 명시적으로 '=== true'를 명시할 필요가 없다
...

// 흔히하는 실수
if (x === 5 || 7 || 10 || 20) { 
// 올바른 표현방식
if (x === 5 || x === 7 || x === 10 || x === 20) {

js 함수

// 일반적인 함수
function myFunction() {
  alert("hello");
}

// 익명 함수
function() {
  alert("hello");
}

// 이벤트 핸들러에 사용되는 익명함수
var myButton = document.querySelector("button");
myButton.onclick = function () {
  alert("hello");
};

// 함수 표현식(변수에 익명함수 대입하기), 이는 변수 호이스팅이 되지 않는다.
var myGreeting = function () {
  alert("hello");
};
myGreeting(); // 함수 표현식의 호출

함수 호출 연산자에 따른 호출 순서의 변경

  /* 함수 호출 연산자(function invocation operator) */
  // btn.onclick = displayMessage(); // ()가 붙을 경우 현재 스코프에서 즉시 실행하게 된다.
  // btn.onclick = displayMessage; // 버튼이 클릭된 이후에만 호출된다.
  btn.onclick = function () { // 함수를 익명 함수 안에 넣어 함수가 즉각적인 스코프(immediate scope) 내에 있지 않게 하여 즉시 호출되지 않게했다.
    // displayMessage("Your inbox is almost full — delete some mails", "warning");
    displayMessage("Brian: Hi there, how are you today?", "chat");
  };

js 이벤트

이벤트란 프로그래밍 시스템에서 일어난 사건이다. 시스템은 이벤트가 발생될 때 자동적응로 취해질 수 있는 매커니즘을 제공한다.

웹의 경우에 이벤트는 브라우저 윈도우 내에서 발생되고 특정한 요소에 딸려있는 경향이 있다. (하나의 요소, 요소들의 집합, HTML 문서, 전체 브라우저 윈도우 ... ) 예를들어 <button> 요소는 발생시킬 수 있는 이벤트를 가지고 있으므로 이벤트 핸들러의 사용이 가능하다.

이벤트들은 이벤트 핸들러를 가지고 있고 이는 이벤트가 발생되면 실행되는 코드 블럭이다. 이 코드 블럭이 이벤트에 응답해서 실행되기 위해 정의되어 있는 것을 이벤트 핸들러를 등록했다고 말한다.

이벤트 리스너는 이벤트의 발생을 감지한다. 이벤트 핸들러는 이벤트에 응답해서 실행되는 코드이다.

 

인라인 이벤트 핸들러

사용을 권장하지 않는다. HTML과 JavaScript는 분리되어서 분석하기 유리하게 두는 편이 좋다. 버튼이 100개 있다면 모든 100개의 버튼을 관리해야 한다. 이는 유지보수에 비효율적이다.

// 부적절한 예
// HTML
<button onclick="bgChange()">Press me</button>
// js
function bgChange() {
  const rndCol =
    "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
  document.body.style.backgroundColor = rndCol;
}

// 좋은 예
const buttons = document.querySelectorAll("button");

for (let i = 0; i < buttons.length; i++) {
  buttons[i].onclick = bgChange;
}
// 또는
buttons.forEach(function (button) {
  button.onclick = bgChange;
});

이벤트 핸들러의 추가와 제거

addEeventListener(이벤트 이름, 핸들러 함수);

  btn.addEventListener("click", function () {
    var rndCol = "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
    document.body.style.backgroundColor = rndCol;
  });

 

removeEventListener는 이벤트 핸들러를 제거한다 (bgChange라는 핸들러가 제거될 것이다.)

https://developer.mozilla.org/ko/docs/Web/API/EventTarget/removeEventListener

btn.removeEventListener("click", bgChange); // (type, listener)

이 방법 말고도 이벤트 핸들러를 제거할 수 있는 방법이 있다. addEventListener()에 AbortSignal을 지정한 후, 나중에 abort()를 호출해서 수신기를 제거하는 것이다. 1. AbortSignal을 addEventListener에 전달한다. 2. 그 뒤 AbortSignal을 소유하고 있는 컨트롤러에서 abort()를 호출한다.

이벤트 핸들러를 AbortSignal로 제거하기 위해서는 아래와 같은 방법으로 이벤트 핸들러를 전달해야한다.

const controller = new AbortController(); // AbortController 객체 생성
btn.addEventListener( // 이벤트 리스너 등록
  "click"
  , function () {
    var rndCol =
      "rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
    document.body.style.backgroundColor = rndCol;
  }
  , { signal: controller.signal },
); // 이 핸들러에 AbortSignal을 전달

그리고 아래와 같은 방법으로 signal이 할당된 이벤트 핸들러를 제거할 수 있다.

controller.abort(); // 이 컨트롤러와 연관된 어떠한/모든 이벤트 핸들러를 제거

 

이벤트 핸들러를 제거함으로써 같은 버튼이 다른 상황에서 다른 동작을 수행할 수 있도록 한다. 또한 addEventListener()는 같은 리스너에 대해 다수의 핸들러를 등록할 수 있다는 것이다.

// functionA는 동작하지 않음
myElement.onclick = functionA;
myElement.onclick = functionB;

// 두 함수 모두 동작함
myElement.addEventListener("click", functionA);
myElement.addEventListener("click", functionB);

AbortController에 대해 조금 더 알아본다.

출처 : https://blog.outsider.ne.kr/1602

출처 : https://mong-blog.tistory.com/entry/JS-AbortController%EB%A1%9C-%EC%9B%B9-%EC%9A%94%EC%B2%AD-%EC%B7%A8%EC%86%8C%ED%95%98%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-dom-%EC%9D%B4%EB%B2%A4%ED%8A%B8

기본 구조는 아래와 같다.

const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener('abort', () => {
  console.log('Aborted? ', signal.aborted); // true
})

setTimeout(() => {
  controller.abort();
}, 100);

0.1초가 경과되면 controller.abort()가 동작된다. 이벤트 리스너에 의해 콘솔이 찍히게 된다.

aborted는 abort()가 실행되어 요청이 취소되었는지의 여부를 알 수 있다.

 

브라우저에서 AbortController로 fetch()를 취소해보자.

httpbin(https://httpbin.org/)을 통해 5초 뒤에 응답이 오는 예제를 작성한다.

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Abort Controller Demo</title>
</head>
<body>
<script>
  const URL = 'https://httpbin.org/delay/5';
  fetch(URL)
          .then((res) => {
            console.log(`Received: ${res.status}`);
          }).catch((e) => {
    console.error(e);
  });
</script>
</body>
</html>

5초 뒤에 응답이 찍힌 걸 볼 수 있다.

 

 

 

이 예제에서 2초 안에 응답이 오지 않으면 요청을 취소해버리는 방법이 AbortController를 사용하는 것이다. 원하는 타이밍에 fetch요청을 취소할 수 있다. fetch의 두번째 인자로 signal 파라미터를 내려준다. 웹 요청 AP나 DOM event에 AbortSignal객체의 signal값을 인자로 넘기면, 요청을 취소할 수 있는 상태가 된다.

const controller = new AbortController();
const signal = controller.signal;

// fetch 요청을 취소할 수 있는 상태
const response = await fetch(url, { signal });  // signal를 넘겨줌
// dom event를 취소할 수 있는 상태
button.addEventListener('click', onClick, { signal }); // signal를 넘겨줌
<script>
const controller = new AbortController();

setTimeout(() => {
  controller.abort();
}, 2_000);

const URL = 'https://httpbin.org/delay/5';
fetch(URL, { signal: controller.signal })
  .then((res) => {
    console.log(`Received: ${res.status}`);
  }).catch((err) => {
    if (err.name === 'AbortError') {
      console.error('Aborted: ', err); // Aborted:  DOMException: The user aborted a request.
      return;
    }
    throw err;
});
</script>

fetch()가 취소되면 AbortError라는 DOMException을 던지기 때문에 취소된 오류와 다른 오류를 구분해서 처리할 수 있다.


이벤트 객체 e

이벤트 객체는 보통 event, evt, e로 표현되며 이벤트 핸들러 함수에 쓰이곤 한다. 이벤트 객체는 이벤트가 발생한 요소 및 이벤트에 대한 정보를 포함하는 JavaScript 객체이다. 이벤트 핸들러 함수 내에서 'e' 매개변수를 사용하여 이벤트 객체를 받아올 때, 'e'는 항상 해당 이벤트가 발생한 요소에 대한 정보를 포함한다.

    const btn = document.querySelector("button");
    function random(number) {
        return Math.floor(Math.random() * (number + 1));
    }
    function bgChange(e) { // 이벤트 객체 e를 통해 'click' 이벤트의 정보에 접근할 수 있다.
        console.log(e);
        const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
        e.target.style.backgroundColor = rndCol; // e.target은 이벤트가 발생한 요소, 즉 버튼을 가르킨다.
        console.log(e);
    }
    btn.addEventListener("click", bgChange);

e를 console로 찍어보면 이처럼 이벤트에 대한 정보를 담고 있는 것을 알 수 있다.

이처럼 이벤트가 어디서 발생했는지를 쉽게 파악할 수 있는 이벤트 객체는, 어디를 수정해야하는지를 일일히 따라가지 않아도 된다는 점에서 유용하게 사용될 수 있다.

const divs = document.querySelectorAll("div");

for (let i = 0; i < divs.length; i++) {
  divs[i].onclick = function (e) {
    e.target.style.backgroundColor = bgChange(); // 수많은 div 중에서, '이 이벤트'가 발생한 div를 가르키므로 실용적이다
  };
}

preventDefault()

  form.onsubmit = function (e) {
    if (fname.value === "" || lname.value === "") {
      e.preventDefault();
      para.textContent = "You need to fill in both names!";
    }
  };

이벤트 버블링과 캡처링,  stopPropagation()

  <body>
    <button>Display video</button>

    <div class="hidden">
      <video>
        <source src="rabbit320.mp4" type="video/mp4">
        <source src="rabbit320.webm" type="video/webm">
        <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
      </video>
    </div>

    <script>

      const btn = document.querySelector('button');
      btn.onclick = function () { // 버튼을 누르면 비디오 창을 표시한다.
        videoBox.setAttribute("class", "showing");
      };

      const videoBox = document.querySelector('div');
      videoBox.onclick = function () { // 비디오 창을 누르면 비디오 창을 숨긴다.
        videoBox.setAttribute("class", "hidden");
      };
      const video = document.querySelector('video');
      video.onclick = function () { // 비디오를 누르면 비디오를 실행한다.
        video.play();
      };
    </script>
</body>

여기까지의 코드로는 비디오를 클릭하면 비디오가 재생되지만, 동시에 비디오 창 안에 귀속된 비디오를 누르는 것이기 때문에 비디오 창이 숨겨진다.
즉 video.onclick을 하는 순간 videoBox.onclick도 함께 실행되는 것이다.
이처럼 부모 요소를 가지고 있는 요소에서 이벤트가 발생되었다면 브라우저는 두개의 단계를 거치게 된다. 캡처링과 버블링이 그것이다. 버블링 단계에서 브라우저는 선택된 요소에 등록된 onclick 이벤트 핸들러가 있는지 검사하고 있다면 핸들러를 실행한다. 그 뒤 바로 다음 조상 요소로 이동하여 같은 일을 반복한다. <html>에 닿을때까지. 캡처링 단계는 그 반대이다. 가장 상위 노드인 <html>에 등록된 onclick 이벤트 핸들러가 있는지 검사하고 실행한다. 그리고 바로 다음 내부 요소로 이동하고 실행하기를 반복하여 실제로 선택된 요소에 닿을때까지 반복한다. 버블링과 캡처링, 두 타입의 이벤트 핸들러가 모두 존재하는 경우에, 캡처링 단계가 먼저 실행되고, 이어서 버블링 단계가 실행된다.

표준 Event 객체는 stopPropagation()라는 함수를 가지고 있다. 핸들러의 이벤트 객체가 호출되었을 때 이는 첫번째 핸들러는 실행되지만 이벤트가 더 이상 위로 전파되지 않도록 만들어 더 이상의 핸들러가 실행되지 않도록 한다.

 


javascript - API

https://tistory-pencilcase.tistory.com/221


javascript - storage

https://tistory-pencilcase.tistory.com/222

 

 

'【 개발 이야기 】' 카테고리의 다른 글

F/E 기초 - API  (0) 2023.10.12
F/E 기초 - JSON  (2) 2023.10.11
프론트엔드 참고 사이트  (0) 2023.10.06
[aws] amplify와 CI/CD  (0) 2023.09.12
[aws] amplify와 severless  (0) 2023.09.11