[JS] promise, 비동기프로그래밍 진짜 이해함 ? 쉽다쉬워 : 열받네

준리·2022년 9월 19일
0

비동기 프로그래밍

목록 보기
2/10

동기 vs 비동기

[JS]동기처리와 비동기처리 그리고 지옥 :: 비동기처리와 관련해 작성한 글인데 코딩을 시작한지 얼마안된 때여서 그런지 내용을 전혀 이해하지 못한 부분이있지만, 맥락은 얼추 맞다. 비동기처리 되는 상황은 //doing some heavy work(network, read files) network 작업이나 파일을 읽을 때 local에서는 timer 함수 등이 있다.

Synchronous : 맛집 줄서기

  • 한번에 하나의 함수만 실행 Only 1 Call Stack & Execution Context
  • 즉, 하나의 함수가 실행되는 동안 blocking.
  • Single Thread in Single Process
  • Runtime → Execution Context → CallStack

Asynchronous : OS와의 협업

  • 한번에 하나의 함수만 실행 Only 1 Call Stack & Execution Context
  • 비동기 함수는 Backgound에 넘김 (Non-blocking)
  • Single Thread But, Multi Process (:Background는 OS Process 의존)
  • Runtime → Execution Context → CallStack → Background → OS → Task Queue → CallStack

    another Queue

    task Queue : setTimeout(), setInterval(), setImmediate()와 같은 task를 넘겨받는다.
    micro task queue : Promise, async/await, process.nextTick, Object.observe
    Task Queue말고 다른 큐가 더 있다고?

Web Workers API 링크 : 웹 워커를 통해 무거운 작업을 분리된 스레드에서 처리하면 주 스레드(보통 UI 스레드)가 멈추거나 느려지지 않고 동작할 수 있습니다.

CallStack, TaskQueue, EventLoop, Background

  • CallStack(호출 스택) 실행되는 함수가 등록되는 Stack.
  • TaskQueue(태스크 큐) 실행되어야 할 콜백 함수가 대기하는 Queue.
  • EventLoop(이벤트 루프) TaskQueue에 있는 함수를 차례로 CallStack으로!
  • Background(백그라운드) 별도 Process로 I/O처리 후 콜백함수(in APIs Container)를 TaskQueue로!

비동기처리는 기존 callstack과 실행컨택스트 환경 runtime 이외에도 OS와 함께하는 background와 enventLoop와 함께 비동기를 처리한다.

😭비동기의 눈물겨운 성장과정

1st) Callback callback hell, error-first callback pattern - ES5 ~
2nd) Promise then, catch, finally - ES2016
3rd) Generator yield & next() - ES2016 ~ ES2017
4th) Async / Await Only 1 Call Stack & Execution Context - ES2017

Callback Hell의 끔찍한 상황을 마주한 JS는 비동기를 간편하게 처리할 수 있도록 지속적으로 개발하고 발전해왔다.

Promise : 기다리기로 약속한다.

Promise is a JavaScript object for asynchronous operation.
Promise MDN, 공식문서

전통적인 비동기처리인 callback pattern의 단점을 극복하기위해 등장했다.

  • 안전하고, 유지보수하기 쉬운 코드 작성 가능
  • promise도 callback이 사용가능하도록 설계됨
  • 호출시 Promise instance 반환 후 resolve & reject

State : pending → fulfilled or rejected
promise 내부의 진행 상태를 관리한다.
아직 작업이 실행되지 않은 상태를 pending이라고 하며,
성공 시 fulfilled || 실패 시 rejected 를 갖는다.
성공과 실패를 모두 포괄해서 나타내는 상태를 settled라고 한다.

Producer vs Consumer

producer : 비동기 생산자

when new Promise is created, the excutor runs automatically.
console.log("doing something...");가 실행되는 이유!

// 1. Producer
// when new Promise is created, the excutor runs automatically.
const 약속약속 = new Promise((resolve, reject) => {
    //doing some heavy work(network, read files)
    console.log("doing something...");
    setTimeout(() => {
        console.log("done, success");
        resolve("hojun");
        // reject(new Error("no network"));
    }, 2000);
});

//console.log
// doing something...
// *2초 후*
// done, success

resolvereject함수를 매개변수로 받고 있는 것을 알 수 있다.
주로 조건문을 활용하여 두개의 함수를 처리하며, 실패할 일이 없는 비동기처리에선reject를 사용하지 않는 경우도 있다.

consumers : 비동기 소비자


// 2. Consumers: then, catch, finally
약속약속
    .then((value) => {
        console.log(value);
    }) // promise를 반환하기 때문에 catch에 담을 수 있음
    .catch((error) => {
        console.log(error);
    })
    .finally(() => console.log("어쨌든 끝남"));

3가지 메서드를 사용할 수 있다.
then : 그래서 어쩌자고?

  • 앞에 약속약속 프로미스의 결과값이 value에 담겨온다. 그래서 우리가 console.log를 찍으면 'hojun'이 나오겠지?

catch : 에러 이리와봐

  • resolve 메서드를 타서 반환할 땐 catch가 사용될리가 없다. 그래서 위 promise 를 바뀌보자.
const 약속약속 = new Promise((resolve, reject) => {
    //doing some heavy work(network, read files)
    console.log("doing something...");
    setTimeout(() => {
        console.log("done, success");
        //resolve("hojun");
        reject(new Error("no network"));
    }, 2000);
});
  • 강제로 reject 메서드를 태워서 no network라고 징징거리게 해보자.

    에러 메세지가 catch 되는 것을 볼 수 있다. 에러를 컨트롤 할 수 있다는 것은 실로 엄청난 일이다.

finally : 아무튼 퇴근임! (6시 정각 알림 느낌)

  • 칼퇴칼퇴칼퇴를 외치며, 무작정 뛰쳐나가는 현대 직장인의 모습이다.

    프로미스의 이행과 거부 여부에 상관없이 처리될 경우 항상 호출되는 처리기 콜백을 추가하고, 이행한 값 그대로 이행하는 새로운 프로미스를 반환합니다.

Promise chaining

// 3. Promise chaining
const fetchNumber = new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
});


fetchNumber
    .then((num) => num * 2)
    .then((num) => num * 3)
    .then((num) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(num - 1);
            }, 1000);
        });
    })
    .then((num) => console.log(num));

then 메서드 안에서 새로 Promise를 호출 할 수 있다. 사실 fetchNumber 프로미스도 다시 호출할 수 있다. (따라서 다시 콜백지옥이 될 확률도 있어보인다)

체인에 더 많은 비동기 동작을 추가할 수도 있는데, 추가 작업이 많아져도 코드가 오른쪽으로 길어지지 않고 아래로만 증가해서 '멸망’의 피라미드가 만들어지지 않습니다.

여러 단계의 then을 통하여 resolve로 내려준 값을 변환하고 다시 api 콜하듯 새로운 값을 보내고 resolve로 돌려받은 값을 출력했다.

프라미스 체이닝이 가능한 이유는 promise.then을 호출하면 프라미스가 반환되기 때문입니다. 반환된 프라미스엔 당연히 .then을 호출할 수 있습니다.

다음 시간에는 Promise 함수를 직접 만들어보도록 하자.


JS Engine과 JS Runtime은 무엇이 다를까?

JS Engine

자바스크립트 코드를 실행하는 프로그램을 의미한다.

  • V8 - Google, chrome
  • Spider Monkey - Netscape, firefox
  • WebKit - Apple Nitro, Safari
  • Chakra - MS, ie, edge(before chromium)

JS Runtime

자바스크립트 런타임이란, 자바스크립트를 사용하기 위한 모든 내용을 담고 있는 컨테이너라고 볼 수 있으며 예시로 브라우저를 들 수 있다. (자바스크립트 실행 환경이라고 불러도 무난하다)
따라서 런타임에는 자바스크립트 엔진이 필수적이라고 볼 수 있다.

  • Browser - Chrome, Safari, Firefox, Brave, Edge, etc
  • Node - Ryan Dhal, V8 Engine
  • Deno - Ryan Dhal, V8 Engine, No npm, ESM only, TS, Authority

결론

그냥 가장 최신 문법 async/await를 쓰면 다 해결되는 줄 알았다. 하지만 어디서든 레거시는 존재하고 내가 알지 못한다고 해서 몰라야하는 건 아니다. promise를 이해하면서 비동기 프로그래밍에 대해 조금 더 알 수 있었고, 왜 내 코드들이 API를 호출하면 처리가 어려웠는지 반성할 수 있었다. 다음은 프로미스를 함수로 직접 만들어보는 시간이다. 까보면 더 쉽게 빌트인객체를 이해할 수 있겠지. (인체를 해부하는 의사가 된 기분은 왜일까)

출처

SSAC 영등포 교육기관에서 풀스택 실무 프로젝트 과정을 수강하고 있다. JS전반을 깊이 있게 배우고 실무에 사용되는 프로젝트를 다룬다. 앞으로 그 과정 중의 내용을 블로그에 다루고자 한다.

참고자료

Promise

JS engine VS JS runtime

profile
트렌디 풀스택 개발자

0개의 댓글