Axios & Promise & async/await

kim_memo·2021년 2월 6일
0

JavaScript

목록 보기
5/5

회사 프로젝트의 대부분은 비동기 통신으로 API연결 작업을 합니다. 찍어내듯이 배웠지만, Promise 객체와 async/await에 대해서 알아보고자 합니다.

우선 이를 이해하기 앞서
1. 동기와 비동기의 차이
2. 싱글 스레드 기반 JS의 비동기 처리 방법에 대해서 알아보도록 하자.

동기와 비동기, 싱글 스레드와 멀티 스레드

동기란?

동시에 일어난다는 뜻입니다. 요청과 결과가 동시에 일어난다는 약속인데, 바로 요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 합니다. 즉 리턴해주기 전까지 사용자는 다른 활동이 불가능하며 기다려야 합니다.

  • 요청한 결과가 한 자리에서 동시에 일어남
  • 작업 처리 단위(transaction)를 동시에 맞춘다.

비동기란?

동시에 일어나지 않는다는 뜻입니다. 요청과 결과가 동시에 일어나지 않을 거라는 약속이죠.

  • 요청한 그 자리에서 결과가 주어지지 않음
  • 노드 사이의 작업 처리 단위를 동시에 맞출 필요가 없다.
  • 반환되는 시간 동안 다른 작업 수행 가능.

JavaScirpt는 싱글 스레드 프로그래밍언어 때문에 비동기 처리가 필수적이다! 하지만 비동기 처리는 그 결과가 언제 반환될 지 알 수 없기 때문에 동기식으로 처리하는 기법들이 사용되어야 한다. 대표적으로 setTimeout, callback, Promise 객체가 있습니다. 비동기 코드를 동기식으로 처리하기에 좋은 기법들이지만, 약간의 문제점을 지녔고 이를 보완하기 위해 async, await이 이를 보완해준다.

싱글 스레드 & 멀티 스레드

싱글 : 하나의 스레드를 갖는 프로세스. 첫번째 작업 마무리 후 두번째 작업을 시작한다.
멀티 : 하나 이상의 스레드를 갖는 프로세스. 두개의 스레드가 두개의 작업을 짧은 시간 동안 번갈아가며 수행하기 때문에 두개의 작업이 동시에 처리되는 것으로 보인다.

비동기적 JavaScript

C, Java, Python을 사용하면 상식적으로 별도의 스레드나 프로세스를 사용하지 않는 이상 먼저 작성된 순서로, 즉 동기적으로 코드가 실행된다. 하지만 자바스크립트는 먼저 실행된 코드의 작업이 끝나기 전에 더 나중에 실행된 코드의 작업이 끝날 수도 있다.(전말 자바스크립트는 왜 이 모양일까)

function first(){
	setTimeout(()=>{
    	console.log('the first function has been called');
    }, 1000)
}

function second(){
	setTimeout(()=>{
    	console.log('the second function has been called');
    }, 500)
}

first();
second();

console 창을 확인해보면 second 함수가 먼저 실행된 게 보인다. 이것이 JavaScript의 비동기성이다. 하지만 JavaScript는 하나의 스레드(Single Thread)기반의 언어이다. 즉, 한번에 하나의 작업밖에 수행하지 못한다는 의미이다. 하지만 Ajax로 데이터를 불러오며 Mouseover 이벤트를 처리하면서 애니메이션도 동작시킨다. 이런 동시성이 어떻게 가능한 것일까?

Call Stack

JavaScript 엔진은 메모리 힙과 단일 호출 스택(Call Stack)을 가지고 있다. 하나의 호출 스택만 가지고 있으므로 단 한번에 단 하나의 함수만 처리가 가능하다. 호출된 함수를 추가(push), 제거(pop)하는 형태를 지니고 있다. 이렇게 JavaScript는 다른 함수가 실행되고 있을 때는 종료 직전까지 다른 작업이 중간에 끼어들 수 없다. 이것을 Run-to-completion이라 한다. 그렇다면 동시 실행이 불가능할까? 하지만 JavaScript는 엔진으로만 돌아가는 것이 아니다.

JavaScirpt RunTime

JavaScript 엔진 밖에서도 실행에 관여하는 요소들이 존재한다. WebApi(DOM, AJAX, SetTimeout)Task Queue, EventLoop들이 있다.

Web API

브라우저에서 제공되는 API이며, AJAX나 Timeout등의 비동기 작업을 실행한다.
1. JavaScirpt에서 SetTimeout과 같은 함수를 실행
2. JavaScript엔진은 WebAPI에 SetTimeout을 요청하는 동시에 Callback까지 전달.
3. CallStack에서는 WepAPI요청 이후 SetTimeout작업이 완료되어 제거.

WebAPI는 방금 요청받은 setTimeout을 완료 후 동시에 전달받은 Callback을 Task Queue라는 곳에 넘겨준다.

Task Queue, Event Loop

Task Queue는 Callback Queue라고도 한다. 큐 형태로 WebAPI에서 넘겨받은 Callback함수를 저장한다. 이 Callback함수들은 자바스크립트 엔진의 CallStack의 모든 작업이 완료되면 순서대로 CallStack에 추가된다.

이 때
1. CallStack이 비어있지 않은지 (실행중인 작업이 존재하는지)
2. TaskQueue에 Task가 존재하는지를 판단하고
3. Task Qqueue의 작업을 CallStack에 옮기는 일을 EventLoop가 작업한다.

EventLoop는 이 작업을 처음부터 끝까지 반복하여 실행한다. 그래서 EventLoop인 것!

while (queue.waitForMessage()){
	queue.processNextMessage();
}

MDN은 EventLoop의 작업을 위와 같은 가상의 코드로 설명하고 있다.

예시 코드

setTimeout(()=>{
	console.log('all task was done');
}, 5000);

5초 뒤에 문장을 출력하는 간단한 코드다. 어떻게 비동기적으로 작동하는지 알아보자!

  1. 코드 실행 후 setTimeout 함수가 실행되면서 Call Stack에는 setTimeout함수가 추가된다.
  2. setTimeout 함수는 자바스크립트 엔진이 아닌, WebAPI가 처리하기에 담겨진 Callback함수를 API로 전달 후 setTimeout 작업을 요청한다.
  3. Call Stack에서는 모든 작업이 완료 되었으니 setTimeout이 제거.
  4. WebAPI는 setTimeout 작업을 실행하며 5000ms를 기다린다.
  5. 5000ms가 지나고, Task Queue로 Callback함수를 전달한다.
  6. Event Loop는 Call Stack이 비어있는지, Task Queue에 작업이 있는지 검사하는 중이며 마침 CallStack에 비어있고, Task Queue에 수행할 작업이 추가되어 있다.
  7. Event Loop는 Taks Queue에 대기하던 Callback함수 하나를 Call Stack으로 보낸다.
  8. Callback함수의 작업도 모두 완료되어 Pop되고 프로그램이 종료된다.

결국 여기서 알 수 있는 점은, JavaScript 엔진은 그저 주어진 코드를 실행하는 온디맨드(on demand)실행 환경이라는 것이다. 그 코드 실행의 스케줄링은 JavaScirpt엔진이 호스팅된 런타임 환경에 맡게되는 것이다.

만약에 Interval이 0이라면?

setTimeout(function(){console.log(1);}, 0);
console.log(2)

위와 같은 경우, 2->1 순서로 실행되는 것을 확인할 수 있다.

코드가 실행될 경우
1. setTimeout이 먼저 실행된 후 CallStack에는 setTimeout이 등록
2. Web API에 setTimeout 작업을 요청함과 동시에 CallStack에는 setTimeout이 삭제되고 console.log(2)가 추가.
3. console.log(2) 작업이 완료되면, TaskQueue에서 대기중이던 console.log(1)(WebAPI에서 TaskQueue로 넘겨진 callback 함수) 작업이 CallStack으로 전달(EventLoop에 의해서)
4. 실행 후 프로그램이 종료된다.

JavaScript는 정말 싱글 스레드일까?

엄밀히 말하자면 자바스크립트 엔진(메인 스레드)이 싱글 스레드인 것이다. (Call Stack) 이는 독립적으로 실행되지 않고, 웹 브라우저나 노드js와 같은 멀티 스레드 환경에 임베디드 되어 실행된다. 때문에 자바스크립트와 Web API, event loop등을 분리하여 말하기 어렵다.

간단하게 정리하자면 자바스크립트는 엔진이 싱글 스레드로 작동, 런타임 환경이 멀티 스레드를 제공하기 때문에 자바스크립트라는 언어는 멀티 스레드 프로세스 작업을 다룰 수 있다!

1. Promise

프로미스는 비동기 작업을 조금 더 편하게 처리할 수 있도록 ES6에 도입된 기능입니다. 이전에는 콜백함수로 처리했지만, 콜백함수로 처리할 경우 비동기 작업이 많아지면 쉽게 난잡해집니다.

function increaseAndPrint(n, callback){
	setTimeout(()=>{
    	const increased = n+1;
        console.log(increased);
        if(callback){
        	callback(increased);
        }
    }, 1000);
}

increaseAndPrint(0, n=>{
	increaseAndPrint(n, n=>{
    	increaseAndPrint(n, n=>{
        	...console.log('end!');
        });
    });
});

비동기적으로 처리해야 하는 일이 많아질수록, 코드의 깊이가 계속 깊어지는 현상이 있습니다. Promise를 사용하게 되면 코드의 깊이가 깊어지는 현상을 방지할 수 있어요!

profile
archive of study

0개의 댓글