javascript 비동기 프로그래밍

남자김용준·2021년 8월 31일
0

비동기 프로그래밍이란?

자바스크립트는 단일 쓰레드에서 동작한다. 즉, 자바스크립트는 한 번에 한 가지 일만 할 수 있다. 싱글 쓰레드만으로 멀티 쓰레드와 같은 프로그램을 만들기 위해서 하는 작업이 비동기적 프로그래밍이다. 사용자 입력 외에 비동기적 프로그래밍을 사용해야 하는 경우는 크게 세 가지가 있다.

  1. ajax 호출을 비롯한 네트워크 요청
  2. 파일을 읽고 쓰는 등의 파일시스템 작업
  3. 의도적으로 시간 지연을 사용하는 기능(알람 등)
console.log(“Before timeout”);
setTimeout(() => {
  console.log("Executed!");
}, 5000);
console.log("After timeout");

위의 코드를 실행시키면
Before timeout
After timeoute
Executed!
의 결과를 얻는다. setTimeout에서 5초 기다리는 동안 After timeout이 먼저 출력된다. 비동기의 가장 큰 목적, 어떤 것도 기다리지 않고 차단하지 않는다는 것이다.

비동기적 프로그래밍에는 콜백, 프라미스, 제너레이터가 있다. 이 글에선 콜백과 프라미스에 대해서만 다루도록 하겠다.

콜백

콜백함수란 파라미터로 함수를 전달받아 함수의 내부에서 실행하는 함수이다. 콜백 함수는 굉장히 많이 쓰이는 패턴이다. 하지만 여러 콜백 함수를 사용하게 되면 아래와 같은 코드 구조를 가지게 된다.

const fs = require("fs");
fs.readFile("a.txt", (err, dataA) => {
  if (err) console.error(err);
  fs.readFile("b.txt", (err, dataB) => {
    if (err) console.error(err);
    fs.readFile("c.txt", (err, dataC) => {
      if (err) console.error(err);
      setTimeout(() => {
        fs.writeFile("d.txt", dataA + dataB + dataC, err => {
          if (err) console.error(err);
        });
      }, 60 * 1000);
    });
  });
});

이런 코드 결과물을 '콜백 헬' 이라고 한다. 이런 문제를 해결하기 위해 나온 것이 프라미스이다.

프라미스란?

프라미스는 콜백의 단점을 해결하고자 만들어졌다. 그렇다고 해서 프라미스가 콜백을 완벽히 대체하는 것은 아니다. 프라미스는 콜백만 사용했을 때 나타날 수 있는 현상이나 버그를 상당 수 해결해준다.

프라미스 기반 비동기적 함수를 호출하면 함수는 프라미스 객체를 반환한다. 프라미스는 성공 혹은 실패 둘 중 한 가지만 일어나느 ㄱ서을 보장한다. 프라미스는 resolve와 reject라는 자체 콜백을 제공하는데 개발자는 이 둘 중 하나를 반드시 호출해야 한다.

resolve(value) - 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출한다.
reject(error) - 에러 발생 시 에러 객체를 나타내는 error와 함께 호출한다.

프라미스 객체는 state와 result 프로퍼티를 갖는다.
state - 처음에는 pending이었다가 resolve가 호출되면 fulfilled, reject가 호출되면 rejected로 변한다.
result - 처음에 undefined였다가 resolve(value)가 호출되면 value, reject(error)가 호출되면 error로 변한다.

프라미스 객체는 then, catch, finally의 세 가지 메서드를 가진다.

.then

promise.then(
  function(result) { /* 결과(result)를 다룹니다 */ },
  function(error) { /* 에러(error)를 다룹니다 */ }
);

첫 번째 인수는 프라미스가 성공했을 때 실행되는 함수이고, 여기서 실행 결과를 받는다.
두 번째 인수는 프라미스가 실패했을 때 실행되는 함수이고, 여기서 에러를 받는다.

.catch

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("에러 발생!")), 1000);
});

// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력

에러가 발생한 경우만 다루고 싶다면 .then(null,errorHandlingFunction)같이 null을 첫 번재 인수로 받을 수도 있고, catch를 사용해서 다룰 수도 있다.

.finally

try{}catch{} 처럼 프라미스에도 finally가 있다. 결과가 어떻든 마무리가 필요하면 finally가 유용하다. finally는 프라미스의 성공, 실패 여부를 몰라도 된다. finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달한다.

chaining

then,catch,finally를 이용하여 프라미스는 체인으로 연결할 수 있다. 프라미스가 완료되면 다른 프라미스를 반환하는 함수를 즉시 호출할 수 있다. 프라미스 체인에서는 어디에서 에러가 생기면 체인 전체가 멈추고 catch 핸들러로 간다.

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;

});

프라미스를 사용하면 기존의 콜백함수보다 깔끔하게 코드작성이 가능하지만, 실행 절차/순서가 복잡하여 가독성이 떨어진다. 따라서 async/await이 등장하였다.

async & await

사용방법이 간단하고 직관적이어서 활용도가 높다. 함수 앞에 async를 붙여주고 비동기로 처리되는 곳에 await을 추가해준다. await이 붙은 뒷 부분은 반드시 프라미스를 반환해야 하고 async함수도 프라미스를 반환해야 한다.

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });

  let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)

  alert(result); // "완료!"
}

f();

참조:[
참조1
참조2
참조3
]

profile
frontend-react

0개의 댓글