Dev-Tino

Dev-Tino 3주차(3):: JavaScript의 비동기 처리 방식(Promise, async, await)

앞부분에서 동기/비동기식에 대하여 간략히 설명하였다. SW에서의 비동기에 대해서는 아래 게시글을 참고한다::

JavaScript는 싱글 스레드 언어(*한 번에 하나의 작업만을 수행할 수 있는 언어. 다른 작업이 중간에 끼어들 수 없으며 기존에 수행하던 작업이 끝나야만 그 다음 작업을 수행할 수 있다.)로 한 번에 하나의 작업만 수행할 수 있다. 이전 작업이 완료되어야 다음 작업을 수행할 수 있다는 것이다. 이는 각 함수와 코드들이 작동하는 일반적인 방식이며, 위에서 아래로 코드가 순차 진행되는 방식을 동기 방식이라 이른다. (이 ‘동기 방식’은 간단하고 직관적이나 작업이 오래 걸리거나 응답이 늦어지는 경우에는 전체적인 성능과 사용자 경험에 영향을 준다는 문제점을 가진다. 이는 전체적인 성능과 사용자 경험에 영향을 준다.)

그러나 이러한 싱글 스레드-동기 방식을 사용하는 언어, JavaScript(자바 스크립트)는 ‘브라우저에서 웹 서버에 요청을 보내거나 받고 있을 때에도 렌더링을 멈추지 않는 등’ 동기식 같지 않은 모습을 보인다. 이는 자바 스크립트의 런타임 환경 덕분이다.

자바 스크립트의 런타임 환경, 브라우저

자바는 동기 방식 언어이다. 이벤트 루프(* 브라우저의 동작을 관리하는 관리자)를 지원하지 않는다. 그러나 브라우저에는 이벤트 루프가 있다. 이벤트 루프 기반의 비동기 방식으로 논-블로킹 IO를 지원하고 동시성을 지원한다. JS에서 비동기 방식을 하기 위해서는 브라우저나 노드JS와 같은 ‘런타임 환경’을 이용하는 것이다.

동기 방식의 고질적인 단점을 해결하기 위해, 자바 스크립트는 런타임 환경의 도움을 받아 세 가지 비동기 처리 방식을 지원하기로 하였다. ‘CallBack’, ‘Promise’, ‘async/await’은 비동기식 방식을 지원하기 위해 도입된 친구들이다.

CallBack 함수(콜백 함수)란

콜백 함수란 ‘다른 함수의 파라미터로써 전달되는 함수’를 의미한다. 이 함수는 태스크가 끝나기 전에 함수가 실행되지 않음을 보장하며 할당된 태스크가 끝난 직후에 실행된다. 이는 비동기 자바스크립트 코드를 작성할 수 있도록 하고 여러 문제와 에러들로부터 안전하게 지켜준다. 이는 ‘비동기식 코드와 동기식 코드를 함께 사용했을 때 실행 순서를 보장할 수 없게 되는 문제’를 예방하기 위한 함수이다.

자바 스크립트에서의 함수는 객체이기에, 우리는 함수의 파라미터로서 객체를 전달할 수 있다. 자바스크립트는 코드를 위에서 아래로 순차적으로 실행하나, 예기치 못한 경우에 의해 코드가 ‘다른 행위가 일어난 뒤 실행되는’ 경우도 있고 순차적으로 실행되지 않을 때도 있다. 이를 비동기 프로그래밍이라 한다.

그러나 콜백은 ‘태스크가 끝나기 전엔 함수가 실행되지 않음’을 보장한다. 이 콜백은 비동기 자바스크립트 코드를 작성할 수 있도록 해주고 여러 문제와 에러들로부터 안전하게 지켜준다.

콜백 함수를 만드는 법

함수의 매개변수로 함수를 삽입한다. 위와 같이 화살표 함수를 넣을 수도 있지만, 아예 다른 함수를 새로 넣을 수도 있다.

콜백 함수 안에 콜백 함수를 다시 한 번 넣을 수도 있지만, 일반적으로 이런 방법은 권장되지 않는다.

콜백 함수의 과도한 사용은 코드의 가독성을 해치는 결과를 낳는다. 구글에 ‘콜백 지옥’이라 검색하면 그와 관련된 사진이 많이 나온다. 콜백 함수를 반복하다보니 화살표 모양의 우스꽝스러운 코드가 생겨나버리는 것이다.

콜백 함수는 비동기를 순차적으로 처리하기 위한 편법일 뿐이지 ‘비동기 전용 함수’는 아니다. 이를 과도하게 사용하면 코드의 가독성을 해치기만 한다. 자바 스크립트의 Promise 객체는 이러한 한계점을 극복하기 위해 만들어졌다.

Promise 객체란?

Promise란 비동기 작업의 성공, 실패와 그 결과값을 나타내는 객체이다. 이를 이용하여 비동기 작업을 쉽고 깔끔하게 연결할 수 있다. 이 때 가장 중요한 점은 Promise가 함수가 아닌 객체라는 점이다. 이는 생성자를 통해 만들어진다. 인자로는 Executor 함수가 들어가는데, 이 Executor 함수는 resolve와 reject라는 두 개의 함수를 매개 변수로 받는 실행 함수이다. Executor 함수는 비동기 작업을 시작하고 모든 작업을 끝낸 후, 해당 작업이 성공적으로 이행되었다면 resolve 함수를 호출하고 중간에 오류가 발생한 경우 reject 함수를 호출한다.

Promise 객체는 다음과 같이 선언한다::

이 경우에 timeAttack이라는 Promise라는 객체는 세 가지 상태를 가진다.

-대기(pending): 아직 실행되지 않은 초기 상태

-이행(fulfilled): 작업이 성공적으로 완료됨.

-거부(rejected): 작업이 실패함.

작업이 성공적으로 이행되었거나 실패했을 때, 어떠한 작업을 해야 한다면 그것은 then 메소드에 의해 실행된다. 이는 callback 함수를 실행한 것과 같은 효과를 낸다. then 메소드는 Promise 객체에 붙어서 사용한다.

then() Method

then 메소드는 promise 객체를 리턴하고 두 개의 콜백 함수(예시의 경우, resolve, reject 함수를 쓴다.) 를 인수로 받는다. successCallback과 failureCallback은 각각 성공(fulfilled), 실패(rejected)했을 때를 위한 콜백 함수이다. 이 then 메소드는 ‘Promise 객체를 리턴’하고 ‘인수로 받은 콜백 함수들의 리턴 값을 이어받’는다. 즉, chaining이 가능하다는 것이다. (이 때 chaining이란 컴퓨터 데이터 처리 조작에서 입출력을 위해 효율이 떨어지지 않도록 하는 채널의 작동 방법이다. 다음 작업이 미리 결정되어있을 때에, 중간에 작동을 중단시키거나 쉬게 두는 시간 없이 계속해서 다음 지시를 연쇄적으로 이어지게 하는 방식이다.)

프로미스 객체 처리

이렇게 만들어진 Promise 객체는 비동기 작업이 완료된 이후에 다음 작업을 연결시켜 진행할 수 있다. 작업 결과에 따라 .then()과 .catch() 메소드 체이닝을 통해 성공과 실패에 대한 후속 처리를 진행할 수 있다.

만약 처리에 성공하여 프로미스 객체 내부에서 resolve(data)를 호출하게 되면, 바로 .then() 으로 이어져 then 메소드의 콜백 함수에서 성공에 대한 추가 처리를 진행한다. 이 때 호출한 resolve() 함수의 매개변수의 값이 then 메소드의 콜백 함수 인자로 들어가 then 메소드 내부에서 프로미스 객체 내부에서 다룬 값을 사용할 수 있게 된다.

반대로 처리에 실패하여 프로미스 객체 내부에서 reject(“Error”)를 호출하게 되면 바로 .catch()로 이어져 catch 메소드의 콜백 함수에서 성공에 대한 추가 처리를 진행한다.

프로미스 객체의 메소드들

.then()

promise 객체의 처리된 결과 값을 콜백 함수의 인자로 받는다. 자세한 설명은 위를 참고한다.

fetch().then()

fetch('api url').then(function(response)=>{})

fetch가 성공할 경우 콜백 함수가 호출되며, 결과를 인자로 받는다.

response.json().then()

response.json().then(function(data)=>{})

response.json()은 promise 데이터 타입을 객체데이터 형태로 콜백함수 인자로 받음(data)

.catch()

.catch(function(reason)=>{})

fetch()가 실패할 경우 콜백함수가 호출됨. 실패 이유를 인자(result)로 받음.

.finally()

finally 메소드는 프로미스를 처리한 후(이행되거나 거부된 후) 호출할 함수를 예약한다. 이 메소드는 즉시 동등한 Promise 객체를 반환하므로, 프로미스 체이닝이 가능하다. 이 메소드를 사용하면 then()과 catch() 처리기 속 코드 중복을 피할 수 있다.

결과에 관계없이 프로미스가 처리되고 나서 무언가를 처리하거나 정리할 때 유용하다. 비슷한 방식으로 사용되는 매개변수, onFinally(*프로미스가 처리된 후 비동기적으로 실행될 함수, 거부된 프로미스를 반환하지 않는 이상 반환값은 무시됨. 함수는 인자 없이 호출됨.)와 비슷하다. (onFinally를 개선한 것이 Finally()이다. onFinally() 콜백은 인자를 받지 않는다. finally() 호출은 보통 외부의 영향을 받지 않고 원래 프로미스의 최종 상태를 변경하지 않는다. 함수를 인라인으로 만들 때, 두 번 선언하거나 변수에 할당할 필요 없이 한 번만 사용해서 전달할 수 있다.)

그러나 이 프로미스도 완벽한 해결책은 아니다. 콜백 함수에게 Callback Hell(콜백 지옥)이 있듯이 Promise에게는 then 핸들러 남용에 의한 Promise Hell이 있다. 프로미스가 여러개 연결되면 코드가 길어지고 복잡해진다. 이를 개선한 방식이 async/await이다. 프로미스 객체를 기반으로 하는 이 방식은 비동기 처리 코드를 마치 동기 코드처럼 작성할 수 있게 해준다. 그 만큼 쉽게 읽고 이해할 수 있어 자주 쓰인다고 한다.

async/await이란?

async/await 함수는 Promise를 리턴하는 함수이다. await 함수는 오직 async 함수 안에서만 쓰일 수 있다. await 함수는 함수의 execution(실행)을 멈추고, promise를 해결한 뒤 함수를 지속한다. 가독성이 좋아질 뿐만 아니라 에러 처리가 간단해진다는 장점을 가져 좋다.

async는 함수 앞에 붙어 해당 함수가 비동기 함수임을 나타낸다. await은 비동기 함수의 실행 결과를 기다리는 키워드이다. async 함수 안에 await 키워드를 사용하면 해당 비동기 작업이 완료될 때까지 코드 실행을 일시 중지하고, 결과를 기다린 뒤 해당 결과를 반환한다.

async/await은 다음과 같이 작성한다::

[참고 자료]

-https://inpa.tistory.com/entry/%F0%9F%8C%90-js-async

-https://wslog.dev/js-event-loop

-https://velog.io/@mieum/Javascript-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

-https://hun-dev.tistory.com/29

-https://www.freecodecamp.org/korean/news/https-www-freecodecamp-org-news-javascript-callback-functions-what-are-callbacks-in-js-and-how-to-use-them/

-https://velog.io/@jaeung5169/%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%99%80-callback-%ED%95%A8%EC%88%98

-https://velog.io/@cyranocoding/2019-08-02-1808-%EC%9E%91%EC%84%B1%EB%90%A8-5hjytwqpqj

-https://www.w3schools.com/js/js_async.asp

-https://velog.io/@minw0_o/JS%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A5%BC-%EC%A7%80%EC%9B%90%ED%95%98%EB%8A%94%EA%B0%80

-https://inpa.tistory.com/entry/%F0%9F%94%84-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B5%AC%EC%A1%B0-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC

-https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC-Promise

-https://velog.io/@minew1995/JavaScript-Promise-%EA%B0%9D%EC%B2%B4

-https://velog.io/@zooyaho/Temp-Title-pl807kua

-https://ko.javascript.info/async-await

카테고리
#기타

댓글 0



추천 포스트