API & Promise & async

21573 단어 JavaScriptJavaScript

동기와 비동기

  • 동기(Synchronous)는 현금 인출기를 사용하는 상황과 비슷하다
  • 동기적 방식은 실행 순서가 확실하다
  • Js는 싱글 스레드 언어이다(Call stack이 하나뿐이다)
  • 싱글 스레드 언어는 현금 인출기처럼 하나 하나 순서대로 처리가 진행된다
  • 비동기 요청을 할 때 비동기 처리를 해줄 수 있는 다른 길들을 WEB API라고 생각하자
  • WEB API를 메뉴판이라 생각하자

비동기 & WEB API

비동기의 포인트는 어떤 요청이 먼저 처리되는지이다.
서버에 두개의 요청을 보냈다고 가정해보자.
먼저 보낸 요청은 무거운 요청으로 1분이 소요되고, 두번째로 보낸 요청은 1초가 소요되는 요청이다.

동기적 방식은 1분이 소요되는 요청을 먼저 보냈다면, 그 요청이 끝날 때 까지 기다리다가 요청이 끝난 후 1초가 소요되는 요청을 진행하는 비효율적인 방법을 사용한다.

카페를 예로 들자면, 카페에서 케익 제작을 주문 받은 후에 커피 주문을 받아도 커피가 먼저 완성된다. 이 것이 효율적으로 처리하는 방법이기 때문이다.

이렇게 효율적으로 처리하기 위해 비동기가 생겼다.

WEB API를 메뉴판이라 생각하자. WEB API는 각 요청에게 개별적인 길을(?)제공해준다.
비동기 방식에서 중요하게 생각해야 할 것은 '무엇이 먼저 끝나는지' 이다.
케익과 커피중 무엇을 먼저 주문했는지에 대한 순서보다 무엇을 먼저 완성하였는지에 따라 손님이 먼저 받는 것이 정해진다.

여기서 중요한 점은 손님이 주문한 음식을 줘야 하는데 손님은 이 음식을 언제 받을 수 있는지 알 수 없다.
그래서 사장은 손님에게 음식이 언제 완성되는지를 알려줘야한다.

완성 되는 시간을 알지 못하면 순서를 보장할 수 없다.
어떤 요청이 먼저 처리되는지가 중요하기 때문에, 명확한 실행 순서가 존재하지 않게 된다. 주문 순서에 상관 없이 먼저 처리가 끝나는 것을 먼저 받는다.


콜백함수

setTimeout()의 첫번째 인자로 콜백을 넘기고 두번째 인자로 몇초 후에 그 콜백을 호출할지 정한다. 아래의 코드는 createPost()가 먼저 실행될 것으로 보인다.
Js는 싱글 스레드 언어이므로 코드가 작성된 순서대로 코드를 실행한다.
순서대로 createPost가 먼저 호출되는데 createPost 바디의 setTimeout이 호출되어 WEB API에 올라간다 .올라간 함수의 처리가 완료되기 전에 WEB API에 올리고 바로 다음 줄에 있는 코드의 함수인 getPosts를 호출한다.
getPosts 바디의 setTimeout이 바로 WEB API로 올라간다. 사실상 동시에 WEB API로 올라가는 것과 동일하다.(컴퓨터가 실행하는 것이기 때문에!) 그래서 getPosts가 creatPost 이후에 WEB API에 올라감에도 불구하고(creatPost이후에 getPosts가 호출되었음에도 불구하고) getPosts가 먼저 처리 완료되어 createPost가 완료되기 전에 완료되었기때문에 화면에 리스트가 두개만 찍힌다.


콜백함수 사용전

setTimeout()은 비동기 함수이다.
나와 다른 사람이 각각 아메리카노와 카페라떼를 주문했다는 가정을 해보자.

두 주문을 비동기 함수라고 생각하면 이 함수들을 어떤 순서로 실행할지 순서를 보장할 수 없다.
눈으로 본 코드로는 createPost이후에 getPosts가 호출될 것 처럼 보이지만 getPosts가 먼저 실행된 것 처럼 실행 순서를 우리가 예측하는 것이 어렵다.

우리의 의도는 createPost로 요소를 생성한 이후 getPosts로 그 요소를 가져오고 싶었지만 의도와는 다르게 요소를 가져오는 것이 더 빨랐기 때문에 요소를 가져와 리스트를 화면에 출력한 후 요소가 생성되었다.

const posts = [
  { title: "Post One", body: "This is post one" },
  { title: "Post Two", body: "This is post two" }
];

const getPosts= () => {
    setTimeout(() => {
    let output = "";
    posts.forEach((post, index) => {
      output += `<li>${post.title}</li>`;
    });
    document.body.innerHTML = output;
  }, 1000);
 };

const creaetPost = (post) => {
  setTimeout(() => {
    posts.push(post);
  }, 2000);
};

createPost({ title: "Post Three", body: "This is post Three" });
getPosts();


비동기 처리

  • 순서를 정해주는 교통경찰 같은 역할을 하는 처리가 비동기 처리이다!
  • 비동기 처리를 하는 방법중 콜백함수를 사용하는 방법이 있다.

콜백함수 사용후

const posts = [
  { title: "Post One", body: "This is post one" },
  { title: "Post Two", body: "This is post two" }
];

const getPosts = () => {
  setTimeout(() => {
    let output = "";
    posts.forEach((post, index) => {
      output += `<li>${post.title}</li>`;
    });
    document.body.innerHTML = output;
  }, 1000);
};

const createPost = (post, callback) => {
  setTimeout(() => {
    posts.push(post);
    callback(post);
  }, 2000);
};

createPost({ title: "Post Three", body: "This is post Three" }, getPosts);

createPost의 두번째 인자로 getPosts함수를 콜백함수로 넣어준다.
createPost를 호출하면 바디의 setTimeout이 호출되어 WEB API로 올라가게 된다. 그 2초 후에 setTimeout의 첫번째 인자인 콜백함수(익명함수)가 호출된다.
createPost의 두번째 매개변수 callback의 인자로 들어온 getposts함수는 그에 따라 반드시 2초 후에 호출된다.

getPosts함수는 위의 코드에서 비동기 처리를 하기 위해 즉, 흐름을 제어하기 위해 createPost의 콜백함수로 사용되었다.

setTimeout의 첫번째 인자인 콜백함수(익명함수) 바디에서는 posts.push()의 처리가 완료된 후에 createPosts의 인자로 들어온 getPosts를 호출하도록 순서가 정해진 것을 확인할 수 있다.
세번째 리스트가 추가된 후에 다음줄에서 getPosts를 호출하기 때문에 순서를 제어할 수 있게 되었다.

setTimeout

setTimeout는 비동기 함수이다.setTimeout의 첫번째 인자로 콜백함수(익명함수)를 넣고 두번째 인자로 콜백함수가 몇 초 후에 호출될지를 넣어준다.
두번째 인자로 어떤 값을 넣을지는 모르지만 반드시 해당 시간 후에 호출이 되어진다.
이것이 비동기이다. 비동기는 순서가 보장되지 않는다.


순서를 보장하려면 비동기 처리를 해야 한다.
비동기 처리를 하는 방식중 하나가 콜백함수이다. 콜백함수가 반드시 동기적으로 동작하는 것은 아니다.
위의 코드는 콜백함수의 바디에 동기 함수 posts.push()를 호출 후 비동기 함수가 포함된 getposts를 콜백함수로 사용하였기 때문에 동기적으로 동작한다.


const createPost = (post, callback) => {
  setTimeout(() => {
    posts.push(post);
    callback(post);
    console.log("끝!");
  }, 2000);
};

하지만 위의 코드와 같이 콜백함수 다음 코드로 console.log()가 추가된다면, 콜백함수로 입력된 getposts가 호출되면 그 바디에서 호출된 비동기 함수 setTimeout이 호출되면서 WEB API로 올라간다.
올린 직후 setTimeout는 비동기 함수이기 때문에 처리를 끝내는 것을 기다리지 않고 다음 줄의 console.log("끝!")을 실행한다.

끝! 이 콘솔에 출력된 후 getPosts의 처리가 끝나 리스트가 화면에 출력되는 것이다.

이처럼 반드시 콜백함수가 반드시 동기적으로 동작하는 것은 아니다.


promise

  • 콜백 함수들을 순차적으로 계속 사용하면 가독성이 떨어지고 에러 처리가 복잡해지는 콜백 지옥이 펼쳐진다.
  • 이러한 콜백 지옥에서 벗어나기 위해 Promise에 대하여 알아보자.
  • promise는 라이브러리였는데, 이 라이브러리를 매우 많은 사람들이 사용했고, JavaScript에서 라이브러리 없이 기본으로 사용할 수 있도록 promise를 추가했다.
  • 그래서 JavaScript는 promise객체가 내장되어 있다.

const increaseAndPrint = n => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const value = n + 1;
      if (value === 5) {
        const error = new Error();
        error.name = "Value is five";
        reject(error);
        return;
      }
      console.log(value);
      resolve(value);
    }, 1000);
  });
};

increaseAndPrint(0).then(n =>
  increaseAndPrint(n).then(n =>
    increaseAndPrint(n).then(n =>
      increaseAndPrint(n).then(n =>
        increaseAndPrint(n)
          .then(n => increaseAndPrint(n))
          .catch(e => console.error(e))
      )
    )
  )
);
  • Promise는 언젠가 완료가 되는 작업의 결과 값을 담는 상자와 같은 역할을 하는 객체이다.

상자가 만들어질 때는 안에 어떠한 내용이 들어갈지 모를 수도 있다.
그래서 then이라는 메소드를 통해 콜백을 등록하고 작업이 끝났을 때 promise 상자 안에 있는 결과 값을 꺼내 추가 작업을 할 수 있다.
Promise 생성자를 통해서 Promise 객체를 만들 수 있다
생성자 함수의 첫 번째 인자에는 resolve가 들어가고 콜백 안에서 resolve를 호출하면 resolve에 인수로 준 값이 곧 Promise 라는 상자에 들어가 있는 최종적인 결과 값이다.
두 번째 인수에는 reject 함수가 들어가고 비동기 작업에서 에러가 발생했을 때 호출하는 함수이다.

좋은 웹페이지 즐겨찾기