JavaScript 기초부터 배우기-6

donggae·2023년 10월 7일
0

JavaScript

목록 보기
6/7
post-thumbnail

먼저 비동기 프로그래밍을 알아보기 전에 동기 처리와 비동기 처리에 대해서 알아보자

1. 동기 처리와 비동기 처리

  • 동기적으로 작업이란?
    -> 순서가 보장 된 처리 방식, 다른 작업을 완료 전 까지 다른 작업을 블로킹(작업 중단)
    하는 처리 방식
  • 비동기적으로 작업이란?
    -> 순서가 보장 되지 않은 처리 방식, 다른 작업이 완료 되기 전에도 블로킹(작업 중단)
    하지 않고 다음 작업을 실행하는 처리 방식

1-1. 동기적 처리 방식

간단한 예시로 동기적 처리 방식에 대해 알아보자

function foo () {
	console.log("foo");
}
function bar () {
	console.log("bar");
}
foo(); // foo
bar(); // bar

위와 같이 어떠한 작업이 끝이 나고 나서 그 다음 작업을 실행 순차적 실행

1-2. 비동기적 처리 방식

간단한 예시로 비동기적 처리 방식에 대해 알아보자

// 위와 같이 실행을 하되 setTimeout으로 바꾸어 실행 해보자
function foo () {
	console.log("비동기에서 foo");
}
function bar () {
	console.log("비동기에서 bar");
}

setTimeout(foo, 0);
bar();

실행을 해보면 비동기에서 bar ,비동기에서 foo 이렇게 나오게 된다.
왜 foo가 먼저 실행이 되지 않고 bar가 먼저 실행이 될까?

비동기 코드의 실행 순서와 동기 코드의 실행 순서의 차이때문이다.

  1. setTimeout(foo, 0)foo를 비동기적으로 실행 되도록 예약
  2. 그에 비해 bar는 동기적으로 실행
  3. 스택(Stack)에서 bar 함수가 실행 후 출력
  4. bar 함수가 실행이 완료 되었기에 스택(Stack)이 비어진 상태
  5. foo 함수가 비어 있는 스택(Stack)으로 이동
  6. foo 함수가 실행 후 출력

이러한 과정을 거치기에 setTimeout 이 0초라고 하더라도 바로 실행 되지 않는 현상이다.

콜 스택(Call Stack)

함수를 호출할 때, 해당 함수의 정보(변수, 매개 변수 등) 스택에 푸쉬(push),
함수가 실행이 완료 되면 해당 정보를 스택에서 팝(pop)되어 제거 -> 스택이 비어짐



2. Promise(약속 아님)

Escape Callback Hell🔥
연속적으로 발생하는 비동기 함수를 처리할 때, 비동기 함수의 결과를 사용하기 위해 콜백 함수가
중첩되어 복잡해지는 현상을 해결해보자

비동기 작업이 가질 수 있는 3가지 상태를 먼저 알아보자

  • Pending(대기 상태) - 현재 비동기 작업이 진행중, 비동기 작업이 실행될 수 없는 문제가 발생한 상태
  • Fulfilled(성공) - 비동기 작업이 정상적으로 완료된 상태 resolve
  • Rejected(실패) - 비동기 작업이 어떠한 이유로 실패한 상태(서버의 응답X, 지연 시간) reject

2-1. Promise가 없는 비동기 처리

a, b의 값을 더하고 그 값을 콜백함수에 전달해서 X2를 하고, 또 다시 -X2를 하는 함수를 콜백함수로 나타냈다.

function taskA(a, b, cb) {
  setTimeout(() => {
    const res = a + b;
    cb(res);
  }, 1000);
}

function taskB(a, cb) {
  setTimeout(() => {
    const res = a * 2;
    cb(res);
  }, 1000);
}

function taskC(a, cb) {
  setTimeout(() => {
    const res = a * -2;
    cb(res);
  }, 1000);
}

taskA(4, 4, (a_res) => {
  console.log("taskA : ", a_res); // taskA : 8
  taskB(a_res, (b_res) => {
    console.log("taskB : ", b_res); // taskB : 16
    taskC(b_res, (c_res) => {
      console.log("taskC : ", c_res); // taskC : -32
    });
  });
});

흠 보기만 해도 조금 복잡하기도 하고 이게 줄이 더 길어지면 더 복잡해 질 것이다.
위의 코드를 promise를 통해 코드를 고쳐보자

2-2. Promise로 코드 개선하기

new Promise((resolve, reject) => {})

  • 비동기 작업이 성공 했을 때 (...resolve) 호출
  • 비동기 작업이 실패 했을 때 (...reject) 호출

그럼 아까 코드를 수정 해보자

function taskA(a, b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res = a + b;
      resolve(res);
    }, 1000);
  });
}

function taskB(a) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res = a * 2;
      resolve(res);
    }, 1000);
  });
}

function taskC(a) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res = a * -2;
      resolve(res);
    }, 1000);
  });
}

taskA(4, 4)
  .then((a_res) => {
    console.log("taskA : ", a_res);
    return taskB(a_res);
  })
  .then((b_res) => {
    console.log("taskB : ", b_res);
    return taskC(b_res);
  })
  .then((c_res) => {
    console.log("taskC : ", c_res);
  });

promise를 통해 코드를 개선하였는데 어떠한 점이 이점인지에 대해 알아보자

  1. promise를 사용함으로 콜백 함수를 직접 전달하지 않아도 된다는 점
  2. then체이닝의 사용 : then을 사용함으로 코드의 가독성이 올라갔다는 점
  3. 구문이 구분 되다 보니 어떤 작업인지에 대한 파악이 쉽다는 점
  4. 위의 코드는 실패에 대한 코드는 다루지 않았지만, 에러 핸들링이 보다 쉬울 것 같다는 점

const 코드끊기 = taskA(4, 4).then((a_res) => {
  console.log("taskA : ", a_res);
  return taskB(a_res);
});

console.log("a")

코드끊기
  .then((b_res) => {
    console.log("taskB : ", b_res);
    return taskC(b_res);
  })
  .then((c_res) => {
    console.log("taskC : ", c_res);
  });

또한 콜백함수와 달리 중간에 코드를 나눠 어떠한 작업을 추가 할 수 있다는 점이 있다.
그렇지만 아직 복잡한 경향이 있다 그렇기에 이번에는 async, await를 통해 코드를 개선해보자



3. async / await

Escape CallbackHell🔥, Escape thenHell🔥

3-1. async

async function makeAsync() {
	return "Make Async";
}

console.log(makeAsync()); // Promise {<pending>}

async를 붙인 함수makeAsync의 결과값이 promise로 나오는 것을 볼 수 있다
-> then을 사용 가능한 비동기 처리 함수가 된다


async function makeAsync() {
  return "Make Async";
}

makeAsync().then((res) => {
  console.log(res); // Make Async
});

return한 값인 "Make Async"가 resolve값으로 쓰이는 것을 알 수 있다.

3-2. await

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function makeAsync() {
  await delay(3000);
  return "Make Async";
}

makeAsync().then((res) => {
  console.log(res); // 3초 뒤Make Async
});
  • await 비동기 함수 앞에 붙이게 되면 비동기 함수를 마치 동기 함수로 사용 할 수 있게 된다.
  • await 함수가 끝나고 나서 뒤에 함수가 실행이 된다.
  • await 함수는 async가 붙은 함수 내에서만 사용 가능하다.

3-3. async, await으로 코드 개선하기

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function taskA(a, b) {
  await delay(1000);
  const res = a + b;
  return res;
}

async function taskB(a) {
  await delay(1000);
  const res = a * 2;
  return res;
}

async function taskC(a) {
  await delay(1000);
  const res = a * -2;
  return res;
}

async function main() {
  try {
    const a_res = await taskA(4, 4);
    console.log("taskA : ", a_res);

    const b_res = await taskB(a_res);
    console.log("taskB : ", b_res);

    const c_res = await taskC(b_res);
    console.log("taskC : ", c_res);
  } catch (error) {
    console.log("Error : ", error);
  }
}

main();
  • async, await를 사용하여 코드를 수정 했는데 확실히 어떠한 작업인지 구분이 간다
  • await을 통하여 비동기 처리 방식 내에서 그 결과를 기다리는 방식으로 작동이 된다.
  • main함수를 통하여 작업이 순서대로 수행되고, 에러핸들링이 된다.
profile
아자자자

0개의 댓글