비동기 처리 - 1. 비동기와 콜백 글 보러 가기

비동기 처리 - 2. Promise 글 보러 가기

비동기 처리 - 3. Promise Chainig | Error Handling 글 보러 가기

비동기 처리 - 4. Promise API 글 보러 가기

비동기 처리 - 5. microtask 글 보러 가기

 


 

개요

 


 

1. async, await 개요

의미

프로미스 문을 보다 가독성 있게 만들어주는 도구

 

특징
  • function 앞에 async 사용
  • async 키워드가 반드시 있어야 함수 내에서 await 사용 가능 : async + await 은 짝꿍!
  • await = Promise ~ .then 이라고 볼 수 있음

 

예시 코드
async function f() {
  let promise = new Promise((resolve, reject) => {  //async 함수 내에 프로미스도 넣을 수 있음
    setTimeout(() => resolve("완료!"), 1000)
  });
  let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)
  alert(result); // "완료!"
}
f();

// 프로젝트에 적용 
async detect(imageData) {
  if (!this._detector) {
      this._detector = await this._createDetector();
      console.log("model loading success!, detector: ", this._detector);
  }
  this._result = await this._detector.estimateFaces(imageData);
  console.log('this._result: ', this._result);
//... 중략
}

 

에러 핸들링 : try ~ catch 문과 함께 사용 가능!
async function f() {
  try {
    let response = await fetch('http://유효하지-않은-주소');
    let user = await response.json();
  } catch(err) {
    // fetch와 response.json에서 발행한 에러 모두를 여기서 잡습니다.
    alert(err);
  }
}

f();

 


 

2. 프로미스 함수와 비교

프로미스
function showAvatar(githubUser) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);
    setTimeout(() => {
      img.remove();
      resolve(githubUser);
    }, 3000);
  });
}

 

async, await
async function showAvatar() {
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));
	// await은 Promise 및 Promise.all과 함께 사용 가능
  img.remove();
  return githubUser;
}
  • .catch 대신 try ~ catch 문 사용 가능 → 가독성 향상
  • 주의할 점 : async, await은 프로미스 기반임

 


 

3. 비동기 관련 추가로 공부하면 좋을 주제 

비동기 처리 - 1. 비동기와 콜백

비동기 처리 - 2.  Promise

비동기 처리 - 3. Promise Chaining | Error Handling

비동기 처리 - 4. Promise API

 


 

개요

 


 

Microtask (마이크로태스크)

의미

JS 엔진이 비동기 작업 결과를 처리하기 위해 사용하는 작업 단위

 

예시

프로미스의 .then .catch .finally 같은 콜백

 

특징
let promise = Promise.resolve();
promise.then(() => alert("프라미스 성공!"));
alert("코드 종료"); // 얼럿 창이 가장 먼저 뜹니다.
  • 마이크로태스크는 Call stack (콜 스택)이 비었을 때, 즉시 실행 됨
    • 콜 스택이 비었음 : 현재 실행 중인 코드 없음 + 콜백 함수의 프로미스 처리가 완료 됨
    • 따라서 위 코드에서 alert(’코드 종료’) 가 현재 실행 중인 코드이므로 실행하고 → .then 이하 코드 실행함
  • 마이크로태스크는 이벤트 루프에서 다음 태스크를 수행하기 전에 실행

 

마이크로태스크 Queue (큐)
  • 마이크로태스크들이 대기하고 있는 큐(queue)
  • 큐이므로 먼저 들어온 작업 실행 (FIFO)

비동기 처리 - 1. 비동기와 콜백 글 보러 가기 

비동기 처리 - 2.  Promise 글 보러 가기 

비동기 처리 - 3.  Promise Chaining | Error Handling 글 보러 가기 

 


 

개요

 

프라미스 API

 

ko.javascript.info

 


 

1. Promise.all

의미 

전달된 프로미스가 모두 요청 이행되면 (하나라도 거부되면 안 됨), 그 결과를 배열로 묶어서 리턴해주는 API

 

필요성
  • 2개 이상의 프로미스를 동시에 실행시키고, 모든 프로미스가 준비될 때까지 기다려야 할 때 사용
  • 예시 : 복수의 URL에 동시 요청 보내고, 다운로드가 모두 완료된 후에 콘텐츠를 처리

 

문법
let promise = Promise.all([...promises...]);

// 간단 예시 
Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(alert); // 프로미스 전체가 처리되면 1, 2, 3이 반환됩니다. 각 프로미스는 배열을 구성하는 요소가 됩니다.

 

처리 과정
  • Promise.all 의 결과로 리턴되는 것 : 배열 안 프로미스의 결괏값을 담은 배열
  • Promise.all 에 전달되는 프로미스 중 하나라도 거부되면, Promise.all 이 반환하는 프로미스는 에러와 함께 거부됨
    → 그러나 이는 지나치게 극단적. 일부 성공 + 일부 실패를 리턴해주는 Promise.allSettled 있음
  • 이터러블 객체가 아닌 일반 값도 Promise.all(iterable)에 넘길 수 있음

 

예시 코드 
Promise.all([
  faceapi.nets.tinyFaceDetector.loadFromUri('static/tensorflow-models'),
  faceapi.nets.faceLandmark68TinyNet.loadFromUri('static/tensorflow-models'),
  //faceapi.nets.faceExpressionNet.loadFromUri('static/tensorflow-models'),
  //faceapi.nets.ageGenderNet.loadFromUri('static/tensorflow-models'),
  faceapi.nets.faceRecognitionNet.loadFromUri('static/tensorflow-models'),
]).then(() => {
  faceapi.env.monkeyPatch({
      Canvas: HTMLCanvasElement,
      Image: HTMLImageElement,
      ImageData: ImageData,
      Video: HTMLVideoElement,
      createCanvasElement: () => document.createElement('canvas'),
      createImageElement: () => document.createElement('img')
  })
  FaceDetector.isFaceapiLoaded = true;
});

 


 

2. Promise.allSettled

의미

일부 요청 성공 결과 + 일부 요청 실패 결과를 모두 리턴 해주는 (일부 실패해도 됨) API

 

예시 코드
let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/Violet-Bora-Lee',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { // (*)
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[num]}: ${result.reason}`);
      }
    });

// 예상 결과 
[
  {status: 'fulfilled', value: ...응답...},
  {status: 'fulfilled', value: ...응답...},
  {status: 'rejected', reason: ...에러 객체...}
]
  • 브라우저 버전이 오래되어 Promise.allSettled 를 지원하지 않는 경우 폴리필을 구현해야 함
  • 폴리필 : 오래된 웹 브라우저에서 최신 기술 및 API를 지원할 수 있도록 하는 코드 또는 스크립트
// 구현한 폴리필 예시 
if(!Promise.allSettled) {
  Promise.allSettled = function(promises) {
    return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({
      status: 'fulfilled',
      value
    }), reason => ({
      status: 'rejected',
      reason
    }))));
  };
}

 


 

2. Promise.race

의미

가장 먼저 처리되는 프로미스의 결과 (이행 or 거부) 를 리턴하는 API

 

예시 코드
Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 예시 결과 : 1

// 프로젝트에 적용 
return Promise.race([
  fetch(resource, Object.assign({signal}, init)).then(response => {  //fetch 함수를 이용하여 네트워크 리소스에 대한 요청 수행 
      clearTimeout(timeoutID);  //fetch 요청이 완료되면 타임아웃을 취소하기 위해 사용됨
      return response;
  }),
  new Promise((resolve, reject) => {
      timeoutID = setTimeout(() => {
          if (controller) controller.abort();
          reject(new Error(`Fetch timed out after ${timeout} ms`));
      }, timeout);
  })
]);
  • Promise.resolve Promise.reject 는 async, await 구문으로 대체 됨

비동기 처리 - 1. 비동기와 콜백 글 보러 가기 

비동기 처리 - 2.  Promise 글 보러 가기 

 


 

개요

 


 

1. Promise Chaining (프로미스 체이닝)

의미

순차적으로 처리해야 하는 비동기 작업이 여러 개 있는 경우 사용하는 기법

 

방법 1 : 프로미스를 리턴
  • resolve (result) 하는 것
  • 예시 코드 
return new Promise((resolve, reject) => {   
  this._detector.estimateHands(imageData).then(result => {
      this._result = result;
      console.log(`this._result: `, this._result);

      this._result.forEach((res) => {
          console.log(`${res.handedness} hand keypoints:`);
          res.keypoints.forEach((keypoint, i) => {
              let x = keypoint.x - 320;
              let y = keypoint.y - 240;
              console.log(`Keypoint ${i}: [${x}, ${y}]`);
          })
      })
      resolve(this._result);
  }).catch(e => {
      reject(e);
  });
})

 

방법 2: .then을 여러 번 사용
new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
  alert(result); // 1
  return result * 2;
}).then(function(result) { // (***)
  alert(result); // 2
  return result * 2;
}).then(function(result) {
  alert(result); // 4
  return result * 2;
});

 

fetch와 프로미스 체이닝 함께 이용 가능
  • fetch
    • JS에서 제공하는 네트워크 요청의 생성, 응답을 다루기 위한 API
    • HTTP 요청을 보내거나 서버에서 데이터를 가져올 때 사용됨
    • Promise 기반의 비동기 방식으로 동작
    • 브라우저 환경 or Node.js 에서 사용 가능
  • 예시 코드 : Promise.race 를 사용하여 fetch 요청에 타임아웃을 설정
return Promise.race([
  fetch(resource, Object.assign({signal}, init)).then(response => {  //fetch 함수를 이용하여 네트워크 리소스에 대한 요청 수행 
      clearTimeout(timeoutID);  //fetch 요청이 완료되면 타임아웃을 취소하기 위해 사용됨
      return response;
  }),
  new Promise((resolve, reject) => {
      timeoutID = setTimeout(() => {
          if (controller) controller.abort();
          reject(new Error(`Fetch timed out after ${timeout} ms`));
      }, timeout);
  })
]);

 


 

2. Error Handling (에러 핸들링)

// 실행 순서: catch -> then
new Promise((resolve, reject) => {
  throw new Error("에러 발생!");
}).catch(function(error) {
  alert("에러가 잘 처리되었습니다. 정상적으로 실행이 이어집니다.");
}).then(() => alert("다음 핸들러가 실행됩니다."));
  • .then 구문 안에서 throw new Error 하고, .catch 구문으로 에러 다시 던져서 처리하면 보다 효과적으로 에러 처리 됨
  • 만약 위처럼 에러 핸들링하지 않는다면, 서버에 에러를 알리지 않은 채 스크립트가 죽고 콘솔창에 메시지가 출력 됨.
    : 이 경우 JS 는 전역 에러를 생성하는데, 다시 복구가 어려움

비동기 처리 - 1. 비동기와 콜백 글 보러 가기 

 

[Asynchronous] 비동기 처리 - 1. 비동기와 콜백

개요 이번 프로젝트 (handpose, face, pose detection) 을 진행하면서 비동기로 작업을 처리하는 경우가 매우 많았음. 특히 모델 로딩, 좌표 예측 에 해당하는 부분이 시간이 오래 걸리는 작업이었기 때문

merrykang.tistory.com

 


 

개요

  • 시리즈 1편의 개요와 동일 
  • 이하에서는 콜백 지옥의 단점을 보완하고자, 비동기 처리를 프로미스로 처리하는 경우를 정리했음. 
  • 이하 내용의 근본적인 출처는 https://ko.javascript.info/promise-basics
 

프라미스

 

ko.javascript.info

 


 

1. Promise 

문법
let promise = new Promise(function(resolve, reject) {
  // executor
});
  • resolve reject
    : JS의 자체 제공 콜백. 따라서 개발자는 이것들을 신경 쓰지 않고, executor 부분만 작성하면 ok
  • executor 부분
    • new Promise 에 전달하는 함수 : executor (실행자, 실행 함수) 의 형태
    • executor 에서는 결과를 즉시 얻든 (동기), 늦게 얻든 (비동기) 상관없이 인수로 넘겨준 콜백 하나 (resolve 또는 reject 중 하나) 를 반드시 호출해야 함

 

리턴되는 Promise 객체의 내부 property(프로퍼티)

State “pending” “fulfilled” “rejected”
Result undefined value 출력 error 출력
  대기 이행 거부
  • 일반적으로 값을 리턴했을 때와는 다른 결과
  • 따라서 프로젝트에서 createDetector() 함수를 실행했을 때 밑과 같은 결과 출력 됨

01
첫 번째 : 프로미스 이행 두 번째 : 프로미스 거부

  • 내부 프로퍼티들은 프로미스 객체의 내부에 있어 개발자가 직접 접근 불가. 따라서 .then .catch .finally 사용해서 접근해야 함

 


 

2. 프로미스 핸들러 : .then .catch .finally

개요
  • 비동기 작업 실행하는 도구들임
  • .then : 프로미스가 이행된 경우 실행
  • .catch : 프로미스가 거부된 경우 실행. 즉 에러 발생한 경우만 처리
  • .finally
    • 프로미스가 처리된 (이행 or 거부) 경우 항상 실행
    • try ~ catch 문의 finally 문과 거의 동일 기능
    • .then 과의 차이점
      1. .finally 핸들러에는 인수가 없음. 따라서 프로미스가 이행되었는지, 거부되었는지 알 수 없음
      2. BUT .finally 에서는 절차를 마무리하는 ‘보편적’ 동작을 수행하기 때문에 성공, 실패 여부를 몰라도 됨
      3. .finally 에서는 자동으로 다음 핸들러에 결과와 에러를 전달함

 

예시 코드
// 프로젝트에 적용 
return new Promise((resolve, reject) => {   
	this._detector.estimateHands(imageData).then(result => {
    this._result = result;
    console.log(`this._result: `, this._result);

    this._result.forEach((res) => {
        console.log(`${res.handedness} hand keypoints:`);
        res.keypoints.forEach((keypoint, i) => {
            let x = keypoint.x - 320;
            let y = keypoint.y - 240;
            console.log(`Keypoint ${i}: [${x}, ${y}]`);
        })
    })
    resolve(this._result);
	}).catch(e => {
	    reject(e);
	});
})

// .finally 문 예시 
new Promise((resolve, reject) => {
  throw new Error("에러 발생!");
})
  .finally(() => alert("프라미스가 준비되었습니다."))
  .catch(err => alert(err)); // <-- .catch에서 에러 객체를 다룰 수 있음

개요

  • 이번 프로젝트 (handpose, face, pose detection) 을 진행하면서 비동기로 작업을 처리하는 경우가 매우 많았음.
  • 특히 모델 로딩, 좌표 예측 에 해당하는 부분이 시간이 오래 걸리는 작업이었기 때문임
  • 그러나 ①비동기 개념에 대한 이론을 매우 오래 전에 배우기도 했고, ②회사의 기존 코드는 Promise 를 활용하여 작성되었는데 나는  async, await  코드 작성법만 알고 있는 상황이어서 주먹구구식으로 따라감. 그래서  Promise 에 대해서도 공부해보고 싶었음 
  • 따라서 비동기 처리에 관한 내용들을 시리즈로 정리할 것임. 이번 글의 모든 코드와 글은 근본적으로  https://ko.javascript.info/callbacks 을 출처로 함 

 


 

1. 비동기 (Asynchronous)

의미
  • 작업이 완료될 때까지 기다리지 않고, 다른 작업을 수행
    = 백그라운드에서 작업을 수행하고, 그 작업이 완료되면 특정 콜백 함수, 프로미스 등을 통해 결과를 처리
  • JS에서 비동기 스케줄링 대표 예시 : setTimeout() , AJAX 요청, 파일 읽기

 

필요성
  • 일반적
    • 대규모 응용 프로그램에서 보다 효율적인 성능 제공
    • 여러 작업을 동시에 수행 가능. 따라서 다수 클라이언트 요청에도 (ex. 네트워크 통신 및 파일 I/O, 이벤트 처리 등) 웹 어플리케이션이 보다 빠르게 동작하고, UX 향상 가능
  • 나의 프로젝트 (구체적으로)
    • createDetector() 함수 : 파일 시스템에 저장된 모델을 불러오는데(I/O 작업) 시간 오래 걸림
    • estimateHands() 함수 : 모델로 예측하는 인공지능 업무 수행하는 데에 시간 오래 걸림
  • 기본적인 비동기 처리 방법 : 콜백

 


 

2. 콜백

의미
  • 어떤 일이 발생했을 때 시스템에서 호출되는 함수 = 나중에 호출할 함수. 따라서 개념 상 주로 비동기 상황 (ex. 이벤트 발생, 비동기 작업 완료) 에서 사용됨
  • 비동기 함수는 콜백을 인수로 반드시 제공해야 함.
    (콜백 : 함수 내 동작이 모두 처리된 후 실행되어야 하는 함수가 들어간 콜백임)
  • 콜백 함수의 의미 : 콜백의 의미와 매우 유사
const fs = require('fs');
// 콜백 함수 : readFile() 함수의 마지막 인자로 전달된 함수
fs.readFile('example.txt', 'utf8', (err, data) => { 
    if (err) {
        console.error('Error reading file:', err);
        return;
    }
    console.log('File contents:', data);
});
console.log('Reading file...');

 

콜백 지옥 (= 멸망의 피라미드)
  • 발생 이유 : 중첩된 콜백 코드가 많아지는 경우 발생
  • 예시 코드
loadScript('1.js', function(error, script) {

  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', function(error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript('3.js', function(error, script) {
          if (error) {
            handleError(error);
          } else {
            // 모든 스크립트가 로딩된 후, 실행 흐름이 이어집니다. (*)
          }
        });

      }
    })
  }
});

 

해결법
  • Promise 문 사용
    • 특히 콜백을 단 한 번만 호출하는 함수인 경우, 프로미스화하여 사용하면 좋음
    • 콜백을 단 한 번만 호출할 때만 프로미스화 해야 하는 이유 : 콜백은 2개 이상의 결과를 가질 수 있지만, 프로미스는 1개의 결과만 가지기 때문
    • 프로미스화 : 콜백을 받는 함수를 프로미스를 반환하는 함수로 바꾸는 것 . 프로미스화는 async, await 과 같이 사용하면 좋음
  • async, await 함수 사용 : Promise를 보다 쉽게 사용하는 도구

 


 

요약

  • 비동기 처리는 시간이 오래 걸리는 작업이어도 백그라운드에서 다른 작업을 수행할 수 있도록 함. 따라서 기능 실행의 효율성 측면에서 필수불가결한 작업임.
  • 비동기 처리의 대표적인 방법인 콜백은 콜백 지옥에 빠질 확률이 매우 높기 때문에, Promise 또는 async, await 함수의 활용이 절실

 

이전 시리즈 글 : 코드 최적화 - 1. this.~ 문의 사용 보러가기 

이전 시리즈 글 : 코드 최적화 - 2. 유효성 검사 보러가기

이전 시리즈 글 : 코드 최적화 - 3. for문과 forEach문

 


 

개요

  • 개요는 시리즈 1과 동일 
  • 이하에서는 간결한 코드 작성하는 몇 가지 팁 정리함

 


 

1. 삼항 연산자 ? :  

  • 조건에 따라 값을 선택하는 자바스크립트의 표현 식
// 기본 구조 
(condition) ? trueExpression : falseExpression;
  • 예시 코드 : 프로젝트에 적용
this._result.forEach( (res) => {
  ctx.fillStyle = res.handedness === "Left" ? "Red" : "Blue";  // 조건식 쓸 때 왼쪽처럼 등호 2번 사용 가능!
  ctx.strokeStyle = "White";
  ctx.lineWidth = 2;
  res.keypoints.forEach(keypoint => {
      this._drawKeypoint(ctx, keypoint);
  });
  Object.keys(FINGER_INDICES).forEach(finger => {
      const points = FINGER_INDICES[finger].map(idx => res.keypoints[idx]);
      this._drawPath(ctx, points, false);
  });
});

// 최솟값 찾기
const a = 5;
const b = 10;
const min = (a < b) ? a : b;
console.log(min); // 출력: 5

 


 

2. && 연산자 활용

  • 값이 존재할 때만 출력
  • 예시 코드
const message = "안녕하세요!";
message && console.log(message);

     


 

3. filter 문 활용

  • 배열에서 특정 조건을 만족하는 값만 필터링
  • filter 문은 배열에서만 사용 가능
  • 예시 코드
const numbers = [1, 2, 3, 4, 5];
// 홀수만 필터링
const oddNumbers = numbers.filter(num => num % 2 !== 0);
console.log(oddNumbers); // 출력: [1, 3, 5]

이전 시리즈 글 : 코드 최적화 - 1. this.~ 문의 사용 보러가기 

이전 시리즈 글 : 코드 최적화 - 2. 유효성 검사 보러가기

 


 

개요

  • 개요는 시리즈 1과 동일 
  • 이하에서는 for문과 forEach문에 대해 구체적으로 정리함

 


 

1. 문법 

  • for
for (let j = 0; j < keypoints.length; j++) {
    let x = keypoint.x - 320;
    let y = keypoint.y - 240;
    console.log(`${keypoint.name}: [${x}, ${y}]`);
}
  • forEach
res.keypoints.forEach((keypoint) => {                
  let x = keypoint.x - 320;
  let y = keypoint.y - 240;
  console.log(`${keypoint.name}: [${x}, ${y}]`);
})

 


 

2. 비교

  for문  forEach문
장점 - 인덱스 제어 가능 - break, continue 사용 가능 - 배열 뿐만 아니라 객체의 속성에 대해서도 순회 가능 - 가독성 향상
단점 - 가독성 떨어짐 - 인덱스 제어 불가 - break, continue 사용 불가 (반복문을 보다 효율적으로 만들어주는 구성 요소) - 일반적으로 객체의 속성 순회 어려움

 

  • 객체의 속성 예시
// 객체 
const myObject = {
  name: 'John',
  age: 25,
  city: 'New York',
  gender: 'Male'
};

// 배열 
const myArray = ['name', 'age', 'city', 'gender']
  • 객체 = myObject, 객체는 key-value로 이루어져 있음, 따라서 객체의 속성 = value = John, 25, New York, Male을 의미함
  • 그러나 forEach 문은 배열을 순회하기에는 적합하지만, 객체의 속성까지 순회 불가
    : forEach 메서드는 처리 대상으로 배열을 기대, 그러나 객체는 key-value 쌍의 집합
  • 보완책
    1. 객체의 key들을 배열로 얻기 → 각 key에 대응하는 value에 접근
// Object.keys() 사용
const keys = Object.keys(myObject);
keys.forEach(key => {
  console.log(`${key}: ${myObject[key]}`);
});

// 프로젝트에 적용 
const FINGER_INDICES = {
    thumb: [0, 1, 2, 3, 4],
    indexFinger: [0, 5, 6, 7, 8],
    middleFinger: [0, 9, 10, 11, 12],
    ringFinger: [0, 13, 14, 15, 16],
    pinky: [0, 17, 18, 19, 20],
};
Object.keys(FINGER_INDICES).forEach(finger => {
    const points = FINGER_INDICES[finger].map(idx => res.keypoints[idx]);
    this._drawPath(ctx, points, false);
});

     

      2. 배열 요소가 객체인 경우, 배열을 순회하면서 객체의 속성에 접근
          ( 1-1의 객체의 key들을 배열로 얻는 과정 생략하여 보다 직관적인 방법 )

/** this._result의 형태 
this._result: 
	0:
		box: {xMin: 133.~, yMin: 140.~, xMax: 346.~, yMax: 391.~, width: 213.~}
		keypoints: Array(468)
			[0 ... 99]  //배열 요소가 객체
				0: {x: 193.~, y: 315.~, z: -33.~, name: 'lips'}
				1: ... 반복
				2: ...
				3: ...
**/

this._result = result;
this._result.forEach((res, i) => {  
  res.keypoints.forEach((keypoint) => {  //res.keypoints는 배열, 배열의 각 요소(keypoint)에 대해 순회, i는 현재 요소의 인덱스
      if (keypoint.name != undefined) {
          let x = keypoint.x - 320;  //keypoint.x 및 keypoint.y 를 사용하여 현재 요소의 속성에 접근
          let y = keypoint.y - 240;
          console.log(`${keypoint.name}: [${x}, ${y}]`);
      }
  })
})

       

      3. for…in 문 사용

  • 객체의 모든 속성에 대해 반복
  • 그러나 hasOwnProperty 를 이용하여 객체 자체의 속성인지 확인하는 것이 중요
  • 즉 프로토타입 체인을 따라 올라가서 속성을 가져오지 않고, 해당 객체의 속성에서만 확인하기 위함
  • for…in 문과 형태가 유사한 for…of 문은 이터러블 객체를 순회하는 데에 유용함.
    ( 이터러블 객체 : 여러 내장 객체. Array, String, Map, Set )
  • 그러나 객체는 원칙적으로 이터러블 객체가 아님. 단지 객체 내부에 배열이 있을 수 있는 것 뿐임
// for...in 문 적용
for (let key in myObject) {
  if (myObject.hasOwnProperty(key)) {
    console.log(`${key}: ${myObject[key]}`);
  }
}

// 이터러블 객체에 for...of 문 적용 
const myString = "Hello";
for (const char of myString) {
  console.log(char);
}

const myMap = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
  ['key3', 'value3']
]);
for (const [key, value] of myMap) {
  console.log(`${key}: ${value}`);
}

const mySet = new Set([1, 2, 3, 4, 5]);
for (const element of mySet) {
  console.log(element);
}

이전 시리즈 글 : 코드 최적화 - 1. this.~ 문의 사용 보러가기 

 


 

개요

  • 시리즈 1편 개요와 동일함.
  • 이하에서는 특정 값의 유효성을 검사하기 위한 코드인 ①if문 ②filter문을 정리함.

 


 

1. if

  • if문으로 유효성 검사 이유
    • 특정 객체가 undefined 또는 null 이 아닐 때만 코드 실행함
    • 따라서 예기치 못한 오류 방지하여 코드 안정성을 높이기 위해 수행
  • 예시 코드
// 예시 1
if (!this._detector) {
    this._createDetector().then(detector => {
        this._detector = detector;
        console.log("model loading success!, detector: ", this._detector);
    })
}

// 예시 2
if (!canvas || !this.isExistContent(this._result)) return canvas;

// 예시 3
if (this._result) {
  this._result.forEach( (res) => {
      ctx.fillStyle = res.handedness === "Left" ? "Red" : "Blue";
      ctx.strokeStyle = "White";
      ctx.lineWidth = 2;
      res.keypoints.forEach(keypoint => {
          this._drawKeypoint(ctx, keypoint);
      });
      Object.keys(FINGER_INDICES).forEach(finger => {
          const points = FINGER_INDICES[finger].map(idx => res.keypoints[idx]);
          this._drawPath(ctx, points, false);
      });
  });
}

 


 

2. filter문

  • filter문으로 유효성 검사 이유
    • 배열에서 특정 조건을 만족하는 값만 필터링
    • filter 문은 배열에서만 사용 가능
  • 예시 코드
const numbers = [1, 2, 3, 4, 5];
// 홀수만 필터링
const oddNumbers = numbers.filter(num => num % 2 !== 0);
console.log(oddNumbers); // 출력: [1, 3, 5]

문제 인식

  • 이번 프로젝트 (hand-pose-detection, face-landmarks-detection, pose-detection) 기능 구현은 완료했음. 
  • 그러나 기능 구현이 된 코드가 매!우! 더러웠음. 난도질 수준... 따라서 본격적으로 코드 최적화를 고민하기 시작함
  • 이하 내용 및 시리즈로 작성할 내용들은 ①기존 회사 코드 ②각종 공식 문서 ③GPT 와 함께 최적화한 코드 중에서, 3번 이상 자주 사용한 내용임
  • 이하 글 및 코드의 근본적인 출처는 https://ko.javascript.info/object-methods#ref-272
 

메서드와 this

 

ko.javascript.info

 


 

1. 의미

  • 현재 객체. 따라서 기본적으로 메서드 내부에서 this 키워드를 사용하면 객체에 접근 가능
let user = {
  name: "John",
  age: 30,
  sayHi() {
    // 'this'는 '현재 객체'를 나타냅니다.
    alert(this.name);
  }
};
user.sayHi(); // John
  • 자바스크립트에서는 모든 함수에 this 사용 가능 (다른 프로그래밍 언어와 상이)
  • this 값은 런타임에 따라 결정됨. = 컨텍스트에 따라 달라짐. = 동일한 함수라도 다른 객체에서 호출했다면 this가 참조하는 값이 달라짐
let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert( this.name );
}

// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;

// 'this'는 '점(.) 앞의' 객체를 참조하기 때문에
// this 값이 달라짐
user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

admin['f'](); // Admin (점과 대괄호는 동일하게 동작함)

 


 

2. 프로젝트에 적용

  • _함수명을 호출할 때, this._함수명() 사용
_createDetector() {
  const model = SupportedModels.MediaPipeHands;
  const detector = createDetector(model, {
      runtime: "tfjs",
      modelType: "lite",
      maxHands: 2, // or 2~10.
      flipHorizontal: false,
      staticImageMode: false,
      detectorModelUrl:
          "/static/tensorflow-models/tfjs-model_handpose_3d_detector_lite_1/model.json",
      landmarkModelUrl:
          "/static/tensorflow-models/tfjs-model_handpose_3d_landmark_lite_1/model.json",
  });
  return detector;
}

// this._함수명 으로 호출 
detect(imageData) {
    if (!this._detector) {
        this._createDetector().then(detector => {
            this._detector = detector;
            console.log("model loading success!, detector: ", this._detector);
        })
    }
//... 중략
}
  • 생성자 함수에서 메서드, 프로퍼티 초기화할 때 사용 : 즉, 메서드는 객체로 this를 참조
class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  run(speed) {
    this.speed = speed;
    alert(`${this.name} 은/는 속도 ${this.speed}로 달립니다.`);
  }
  stop() {
    this.speed = 0;
    alert(`${this.name} 이/가 멈췄습니다.`);
  }
}

let animal = new Animal("동물");

 

+ Recent posts