모듈과 비동기 처리

김영준·2023년 6월 22일
0

TIL

목록 보기
8/91
post-thumbnail

import

export 키워드로 내보내진 변수, 함수 등등을 불러올 수 있는 키워드

import defaultExport from "module-name";

module-name 내에 export default로 내보내진 것을 가져온다. 보통 컴포넌트들을 이렇게 사용한다.

import * as allItems from "module-name";

module-name 내에서 export 된 모든 것을 모두 가져온다.
as 이후 이름은 중복되지만 않으면 자유롭게 정할 수 있다.

import {loadItem} from "module-name";

module-name 내에서 export 된 것 중에 특정 값만 가져온다.
사용할 라이브러리만 가져오는 것이 효율적이다.

import {loadItem as loadSometing} from "module-name"

module-name 내에서 export 된 것 중에 특정 값만 이름을 바꿔서 가져온다.
loadItem을 loadSometing으로 가져온다.

import App, {printToday} from "module-name"

export default로 된 모듈을 가져오는 것과 export로 된 모듈을 가져오는 것을 같이 import 할 수 있다.

module의 장점

  • import를 사용하면 스크립트 의존성을 훨씬 간편하게 관리할 수 있다.
  • 각 JS별로 사용되는 모듈을 명시적으로 import 해오기 때문에, 사용되거나 사용되지 않는 스크립트를 추적할 수 있다.
  • script 태그로 로딩하는 경우 불러오는 순서가 중요하지만, import로 불러오는 경우 순서는 무관하다.
  • script src로 불러오는 것과 다르게 전역 오염이 일어나지 않는다.

module의 주의사항

  • 웹 서버가 필요하다.
  • from 이후 모듈 이름에 .js를 꼭 붙여야 한다.

비동기 처리

특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성이다.
동기: 10 + 10 + 10 = 30
비동기: 10 + 10 + 10 = 10

비동기 예시

  1. addEventListener 함수
    두 번째 인자로 넘겨진 함수는 바로 실행되지 않고, 이벤트 리스너가 정의한 이벤트가 발생할 때 실행된다.
function onButtonClick() {
  alert("눌렀군요");
}

document.querySelector(".save-button").addEventListener("click", onButtonClick);

  1. setTimeout과 setInterval
    첫 번째 인자로 넘겨진 함수는 바로 실행되지 않고, setTimeout 혹은 setTimeout의 시간만큼 지난 후에 실행된다.
function work() {
  console.log("work!");
}

setTimeout(work, 1000); // 1초 후 실행
setInterval(work, 5000); // 5초마다 반복

console.log("work process");
// 출력 결과

work process
work!
work!

추가로 setTimeout의 시간을 0으로 넣거나 지정하지 않는다고 해도 바로 실행되지 않는다.
function work() {
  console.log("작업 시작");
  setTimeout(function () {
    console.log("작업 중...");
  }, 0);
  console.log("작업 종료");
}
// 출력 결과

작업 시작
작업 종료
작업 중...

  1. XMLHttpRequest(XHR)
    데이터를 비동기로 요청하고, 요청 후의 동작을 비동기로 처리한다.
function request(url, successCallback, failCallback) {
  const xhr = new XMLHttpRequest();
  xhr.addEventListener("load", (e) => {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        successCallback(JSON.parse(xhr.responseText));
      } else {
        failCallback(xhr.statusText);
      }
    }
  });
  xhr.addEventListener("error", (e) => failCallback(xhr.statusText));

  xhr.open("GET", url);
  xhr.send();
}

만약 순차적으로 처리해야 하는 비동기 작업이 더 많아지면?

콜백 지옥에 빠지게 된다.

물론 동기적으로 코드를 작성할 수도 있다.

하지만 자바스크립트는 싱글 스레드이기 때문에 동기적으로 작성하면 요청 후 응답이 오기 전까지 브라우저가 굳어버린다.

예를 들어서 API를 조회하는 데 10초가 걸린다면 10초 동안 브라우저는 먹통이 될 것이다.

따라서 비동기적으로 작성하는 코드가 좋은 코드라고 볼 수 있다.

Promise

Promise는 비동기 작업을 제어하기 위해 나온 개념으로, 콜백 지옥에서 어느 정도 벗어날 수 있게 해준다.

promise로 정의된 작업끼리는 연결할 수 있으며, 이를 통해 코드의 depth가 크게 증가하지 않는 효과가 있다.

const promise = new Promise((resolve, reject) => {
  // promise 내부에서 비동기 상황이 종료될 때, resolve 함수 호출
  // promise 내부에서 오류 상황일 때, reject 함수 호출
});

Promise에서는 then을 이용해 비동기 작업 이후 실행할 작업을 지정합니다.

function asyncPromiseWork() {
  // code...
  return new Promise((resolve, reject) => {
    // code...
    return resolve("complete");
  });
}

asyncPromiseWork().then((result) => console.log(result));

Callback 과의 차이

Promise의 then 내에서 Promise를 return 할 경우 이어진다.

// Callback
callbackWork((result) => {
  callbackWork((result) => {
    callbackWork((result) => {
      callbackWork(result);
    });
  });
});

// Promise
promiseWork()
  .then((result) => {
    return promiseNextWork(result);
  })
  .then((result) => {
    return promiseThirdWork(result);
  })
  .then((result) => {
    return promiseFinalWork(result);
  });

Promise chain 중 작업이 실패했을 경우 .catch로 잡을 수 있다.

catch를 작성하지 않을 경우 promise chain 중 에러가 발생했을 때 chain이 멈추니 가급적 작성하는 것이 좋다.

promiseWork()
  .then((result) => {
    return promiseNextWork(result);
  })
  .then((result) => {
    return promiseThirdWork(result);
  })
  .then((result) => {
    return promiseFinalWork(result);
  })
  .catch((e) => {
    alert("에러가 발생했습니다.");
  });

성공과 실패 여부와 상관없이 호출해야 하는 코드가 있다면 finally에서 처리한다.

promiseWork()
  .then((result) => {
    return promiseNextWork(result);
  })
  .then((result) => {
    return promiseThirdWork(result);
  })
  .then((result) => {
    return promiseFinalWork(result);
  })
  .catch((e) => {
    alert("에러가 발생했습니다.");
  })
  .finally(() => {
    alert("작업이 끝나면 무조건 실행합니다.");
  });

Promise 내장 함수들

promise.all(iterable)

여러 promise를 동시에 처리할 때 유용하다.

const promise1 = delay(1000)
const promise1 = delay(2000)
const promise1 = delay(3000)

Promise.all([promise1, promise2, promise3]).then(() => {
	// promise1, promise2, promise3이 모두 처리된 이후 호출
})

promise.race(iterable)

여러 promise 중 하나라도 resolve 혹은 reject가 되면 종료된다.
거의 사용하지는 않는다.

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min)) + min;
}

const promise = [1, 2, 3, 4, 5].map((n) => {
  const delayTime = getRandomInt(1000, 5000);
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`${n}번 고양이 완주!`);
      resolve(`${n}번 고양이 승리!`);
    }, delyaTime);
  });
});

Promise.race(promises).then(message => console.log(message))

출력 결과

5번 고양이 완주!
5번 고양이 승리! // resolve
2번 고양이 완주!
4번 고양이 완주!
1번 고양이 완주!

Promise.any(iterable)

여러 promise 중 하나라도 resolve 되면 종료된다.
race와 다르게 reject는 무시한다.

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min)) + min;
}

const promise = [1, 2, 3, 4, 5].map((n) => {
  const delayTime = getRandomInt(1000, 5000);
  return new Promise((resolve, reject) => {
    if (n === 1){
      return reject(`${n}번 고양이 기권!`)
    }
    
    setTimeout(() => {
      console.log(`${n}번 고양이 완주!`);
      resolve(`${n}번 고양이 승리!`);
    }, delyaTime);
  });
});

Promise.any(promises).then(message => console.log(message))

출력 결과

// 1번 고양이는 무시된다.

4번 고양이 완주!
4번 고양이 승리!
3번 고양이 완주!
5번 고양이 완주!
2번 고양이 완주!

Promise.allSettled(iterable)

여러 Promise들이 성공했거나 실패했거나 상관없이 모두 이행된 경우를 처리할 수 있다.

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min)) + min;
}

const promise = [1, 2, 3, 4, 5].map((n) => {
  const delayTime = getRandomInt(1000, 5000);
  return new Promise((resolve, reject) => {
    if (n % 2 === 0) {
      return reject(`${n}번 고양이 완주 실패!`);
    }

    setTimeout(() => {
      resolve(`${n}번 고양이 완주!`);
    }, delyaTime);
  });
});

Promise.allSettled(promise).then((results) => console.log(results));

출력 결과

// 5번 다 then을 실행
(5) [{...}, {...}, {...}, {...}, {...}]

0: {status: "fulfilled", value: "1번 고양이 완주!"}
1: {status: "rejected", value: "2번 고양이 완주 실패!"}
2: {status: "fulfilled", value: "3번 고양이 완주!"}
3: {status: "rejected", value: "4번 고양이 완주 실패!"}
4: {status: "fulfilled", value: "5번 고양이 완주!"}

Promise.resolve

주어진 값으로 이행하는 Promise.then 객체를 만든다.

반환하는 값을 promise 타입으로 만들 때 유용하다.

주어진 값이 Promise인 경우 해당 Promise가 반환된다.

const cached = {
  roto: "bassist",
};

const findMember = (memberName) => {
  if (cached[memberName]) {
    return Promise.resolve(cached[memberName]);
  } // cache가 있으면 promise로 리턴

  return request(`/members/${memberName}`).then((member) => {
    cache[member.memberName] = memberName;
    return meberName; // cache가 없으면 api 호출
  });
};

findMerber("roto").then((memberName) => console.log(memberName));

Promise.reject

주어진 값으로 reject 처리 된 Promise.then 객체를 만든다.

주어진 값이 Promise인 경우 해당 Promise가 반환된다.

강제로 reject를 시켜야 되는 상황에 사용할 수 있다.

거의 사용하지 않는다.

new Promise((resolve, reject) => reject());

async, await

Promise가 callback depth를 1단계로 줄여주긴 하지만, 여전히 불편하다.

코드의 실행이 위에서 아래로 실행되는 것이 아니기 때문에 코드의 가독성을 어렵게 만드는 부분이 있다.

// Promise
const delay = (delayTime) => {
  rerturn new Promise(resolve => setTimeout(resolve, delayTime))
}

const work = () => {
  console.log("work run");
  delay(1000)
    .then(() => {
      console.log("work 1 complete.");
      return delay(1000);
    })
    .then(() => {
      console.log("work 2 complete.");
      return delay(1000);
    })
    .then(() => {
      console.log("work 3 complete.");
      return delay(1000);
    })
    .then(() => {
      console.log("work all complete!");
    });

  console.log("work running...");
};

work();

출력 결과

work run
work running
work 1 complete.
work 2 complete.
work 3 complete.
work all complete!

하지만 async, await를 사용하면 Promise를 비동기로 실행하면서 동기 코드처럼 보이게 작성할 수 있다.

const work = async () => {
  console.log("work run");

  await delay(1000);
  console.log("work 1 complete.");

  await delay(1000);
  console.log("work 2 complete.");

  await delay(1000);
  console.log("work 3 complete.");

  await delay(1000);
  console.log("work all complete!");
};

work();

async await 사용 방법

  • async가 함수 앞에 붙어야지 함수 내부에서 await를 사용할 수 있다.

  • async 키워드가 붙은 함수는 실행 결과를 Promise로 감싼다. (반환 값이 자동으로 Promise 타입으로 변환됨)

  • 에러 처리는 try catch 문을 사용하면 된다.

// function 키워드를 사용하는 경우
async function asyncFunction() {
  const res = await request(...)
}

// arrow function을 사용하는 경우
const asyncFunction = async () => {
  const res = await request(...)
}

API 호출 예시

try {
    const data = await request(`https://kdt-frontend.programmers.co.kr/comments?todo.id=${id}`);
    this.setState({
      ...this.state,
      comments: data,
    });
  } catch (error) {
    // promise의 .catch와 비슷한 역할
  } finally {
    // promise의 .finally와 비슷한 역할
  }
profile
꾸준히 성장하는 개발자 블로그

0개의 댓글