Javascript에서의 비동기처리 방식에 대해 알아보자 (스테픈 2km완료)

image.png

오늘은 Javascript에서의 비동기처리 방식에 대해 알아보고자 한다.

#1.동기 비동기의 개념

image.png

image.png

동기란 무엇일까?

동기를 번역하면 위와 같이 '동시성'을 뜻한다.

그렇다면 반대는 동기가 아닌것이 될것이다

image.png

비동기는 영어로 'Asynchronus' , 줄여서 async로 쓰인다.

동기에 대해서 풀어서 설명하자면 코드의 실행과 동시에 결과를 확인할 수 있으며

데이터의 요청과 그에따른 결과가 한 자리에서 동시에 일어나는것이라 정리할수 있겠다.

연애로 비유하자면
서로 호감이있는 상대방끼리 카톡으로 티키타카를 보여주는것과 같다

내가 하나 보내면 그에 대한 대답을 바로바로 전달해준다

image.png

그럼 비동기는?

image.png

위 사진처럼

내가 보낸 요청에 대한 결과를 언제 받을 수 있을지 알수 없는 그것이 바로 '비동기'라고 볼 수 있다

이처럼 비동기는 동기와는 다르게 요청과 그에따른 결과가 동시에 일어나지 않는다.

#2.동기 비동기의 차이 - 코드블락

동기 비동기를 코드를 통해 좀더 이해해보도록 하자

image.png

위 코드의 실행 순서는 1 -> 2 -> 3 -> 6 이렇게 나올것이다.

우리는 이렇게 최종 결과값인 '6'이 바로 찍히는 것을 확인할수 있다.

추가적으로 위 예제에 중간에 7억번정도 반복되는 반복문이 동작하고 그 뒤에 콘솔을찍게되면

08.05.2024 10_08.gif

함수 내부에서 반복문이 돌아 작업이 지연되니 뒤의 결과인 '6'이 느리게 찍히는걸 볼수가 있다.

우리는 '7억번' 문구가 7억번이 찍히기 전까지는 '6'을 확인할 수 없는것이다.

왜 이렇게 동작할까?

지금은 내부 함수가 7억번정도만 반복하기 때문에 고작 몇초정도만 기다리면

결과를 볼 수 있지만 만약 이러한 작업이 1시간 혹은 다음날까지 계속되어 결과가 언제 나올지 모른다면

사용자에게 엄청나게 불편한 경험을 하게 될것이다.

앞서 동기는 동시성때문에 코드의 실행과 동시에 결과를 확인할 수 있다고 이야기 했다.

자바스크립트가 이렇게 동작하는 이유는 싱글쓰레드 기반의 언어이기 때문인데

image.png

사진출처 : https://poiemaweb.com/es6-promise

싱글쓰레드를 단순하게 설명하면 한번에 하나의 작업만 처리가 가능한 사람이라고 생각하면 된다.

그럼 우리가 함수를 실행시키게 되면 해당 함수가 끝날때 까지는 사실상 다음 작업을 할 수 없는게 맞다.

이를 블록킹이라고 한다.

싱글쓰레드는 사실상 동기적으로 동작하고 모든 코드가 블록킹된다고 생각하면 된다.

그럼 자바스크립트에서 비동기는 어떻게 처리가 될까?

단순하게 생각하면 내가 못하는 일을 남에게 부탁하면 된다.

나는 한가지의 일밖에 못하는데 이 일을 하루종일 붙잡고 있다면

사용자에게 너무 불편하기 때문에

이러한 일들을 다른 사람에게 부탁하고 나는 단순한 일을 순서대로 진행해서 처리한다고 보면 어떨까?

사용자 입장에서는 크게 불편함을 못느끼지 않을까?

이런 느낌으로 별도의 쓰레드에서 복잡한 과정을 처리하도록 개발되었다.

그래서 싱글 쓰레드인 자바스크립트가 비동기작업을 처리할 수 있게 된것인데

이 사진처럼 동기방식은 하나의 작업이 끝나야 다음작업으로 이어나간다면

논 블로킹은 작업이 끝나지 않고 이어나가는 병렬적인 처리방식이다.

블로킹 논 블로킹을 이해하려면 앞서 말했던 싱글쓰레드인 자바스크립트에서
병렬적으로 작업을 처리하는 방법에 대해서 이해를 해야한다.

자바스크립트가 동작하는 환경은 브라우저 혹은 node.js등이 있을 수 있다.
자바스크립트에서는 하나의 쓰레드만 있지만 브라우저나 node.js에 작업을 부탁해서 해당작업이 처리되면 거기에 대한 결과를 받아와서 해당결과를 우리에게 보여주는 과정으로 비동기 작업을 처리하기 때문에 비동기, 논블라킹이 되는것이다.

예시로 코드를 한번 작성해봤다.

위의 7억번 동작하던 코드와 거의 비슷한 방식이지만

반복문이 비동기로 동작하도록 promise 객체를 사용해서 반복문을 작동시켰다.

promise는 비동기 작업을 처리하는 객체이기 때문에
싱글 스레드 기반인 자바스크립트는 본인이 해당 작업을 처리하는게 아닌
구동환경이 어디에 있는지에 따라 다르겠지만 node.js에서 아래의 작업을 진행시키는 경우

1 -> node.js환경에 Promise의 작업을 시킴 -> 2 -> 3 ->6 -> node.js에서 작업이 완료된 Promise의 결과값 반환

이러한 흐름으로 비동기 작업이 처리가 되었을것이다.

한가지 더 예를 들어보겠다.

위 코드는 5억번 반복되는 작업은 비동기로 동작하고 7억번 반복되는 작업은 동기방식으로 처리가 되도록 코드를 구성했다.

비동기 작업인 5억번 작업은 node.js 등의 다른 쓰레드에 부탁하고
7억번 반복작업에서 메인 쓰레드에서 작업이 끝날때까지 처리하고 진행된다.
그래서 약간의 블로킹이 발생하는걸 볼 수 있다.
이렇게 '동시성'을 보장하기 위해 코드의 실행후 결과가 올때까지 다음 코드를 실행하지 않고 블락되는 부분이 싱글쓰레드 언어인 자바스크립트의 특징이며
코드를 동기적으로 처리하기 때문이다.

그에 반해 비동기 코드는 자바스크립트에서 본인이 처리하지 않고 다른 쓰레드에 처리해달라고 부탁하고 나머지 작업을 이어 나가는걸 볼 수 있다.

이렇게 동기 비동기, 블락킹 논블라킹에 대해서 간단하게 알아봤다.

image.png
추후 포스팅에서 자바스크립트는 싱글스레드이지만 비동기 작업 처리가 가능한 이유등에 대해서 한번더 정리하도록 하겠다.

#3.왜 비동기 방식을 사용하는가?

앞서 예시로 들었던 코드처럼 몇초 걸리지않는 작업이라면 상관없으나
image.png
당신이 예전 여자친구에게 보냈던 문자처럼

언제 답이 올지 알수가 없는 상황이라면

답이 올때까지 기다리기만 한다면

평생 혼자 살아야 할지도 모른다.

하지만 당신이 비동기에 대해서 위의 글을 통해 조금이나마 이해를 했다면

문자에 대한 응답이 올때가지 가만히 기다리는게 아니라

다른사람과 만남을 이어가거나 다른 일을 하고 있을것이다.

이를 그림으로 표현하자면

image.png

이렇게 표현된다.

하나의 결과값이 올때가지 기다리는게 아니라

동시에 작업을 진행하고 그에대한 결과값이 언제 올지는 모르지만 오는대로

병렬적으로 일을 처리하는 방식이다.

우리가 사용하는 인터넷의 작업은 대부분 서버에 데이터를 요청하고 요청에 대한 결과를 받아서 우리에게 보여주는 방식으로 구현된다.

서버의 응답이 언제 우리에게 올지는 아무도 알 수 없기때문에

우리는 비동기 방식에 익숙해져야한다.

#4.Async,Promise,Await

자 이제 본격적으로 위에서 기술한 '비동기'작업에 대해서 자바스크립트를 사용해서 어떻게 처리해야하는지 알아보자

image.png

async와 await는 ES2017(ECMAScript 8)부터 추가된 자바스크립트의 비동기 처리 방식 중 하나이다.

async란 함수 앞에 표기하는 약속어로 async가 붙은 함수는 비동기임을 뜻한다.

간단한 예시로 사용해보자

image.png

async의 사용법은 함수 앞에 async를 붙여주면 된다.

그럼 결과가 어떻게 반환되는지 확인해보자

image.png

async가 붙은 함수는 실행시 일반적으로 함수 내부의 코드가 실행되는 형태가 아닌 Promise를 반환한다.

이때 반환된 promise를 통해 우리는 비동기 함수의 결과를 받게 된다.

async가 붙은 함수는 반드시 프로미스를 반환하고, 프로미스가 아닌 것은 프로미스로 감싸 반환한다.

이때 반환된 promise를 통해 우리는 비동기 함수의 결과를 받게 된다

다음으로 Promise 에 대해 알아보자

image.png

Promise는 비동기 처리를 위한 하나의 패턴이다

image.png

mdn Promise 문서를 읽어보면

비동기 작업의 최종완료 또는 실패를 나타내는 객체라고 한다.

프로미스객체가 어떻게 생겼는지 알아보기 위해 콘솔에서 호출해보겠다.

image.png

promise를 찍어보면 catch, construcor, finally, then 등의 메서드와 생성자 등이 보인다.

image.png

프로미스를 사용할 때 알아야 하는 가장 기본적인 개념이 바로 프로미스의 상태(states)이다.

여기서 말하는 상태란 프로미스의 처리 과정을 의미한다.

new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖는데

  • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
  • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

방금 내가 호출한 것 처럼 Promise 객체를 호출하면 대기상태가 된다.

image.png

여기서 다시 async로 작성했던 함수를 보자

async로 함수를 만든뒤 실행시키기만 했을뿐인데

Promise 객체를 생성했을때와는 전혀 다른 부분이 있다.

Promise로 객체를 생성하면 바로 실행되지만 async로 함수를 만든경우

함수를 실행해야지만 결과값을 반환한다는점

그리고 promise는 pending 상태로 남아있다는 점이 다르다는걸 알 수 있다.

promise 사용법을 조금더 확인해보면

image.png

프로미스는 인자로 콜백함수를 받는다. 이 함수를 공식문서에서는 executor라고 부르는데

executor는 첫번째 인자로 성공했을때 함수, 두번째 인자로 실패했을때 함수를 갖는다.

이를 정리하면 위 사진과 같다.

image.png

자 그럼 promise가 성공했을때와 실패했을때를 가정하고 실행을 해보겠다.

image.png

앞서 말했던 프로미스의 상태에 맞춰 이행상태와 실패 상태로 나뉘게 되었다.

처음 이야기 했듯 promise는 최종완료 혹은 실패를 나타낸다고 이야기 했다.

그럼 이렇게 프로미스의 상태가 이행되었을때 어떻게 처리를 할 수 있을까?

image.png

앞서 프로미스 객체를 호출해서 콘솔에 찍었을때 몇가지 메서드를 확인 할 수 있었다.

image.png

프로미스는 후속 처리를 위해 catch , then, finally 메서드를 제공한다.

  • 모든 후속처리 메서드는 결과값으로 프로미스를 반환한다.

프로미스의 메서드에서 가장 많이 사용되는 것이 바로 .then이다

then은 두개의 콜백함수를 인수로 전달 받는다.

image.png

앞의 콜백함수는 성공했을때를 나타내고 뒤의 콜백함수는 실패했을경우 사용된다.

뒤의 콜백에서는 에러를 처리할때 사용할 수 있다.

.catch는 단 한개의 콜백함수를 인수로 받는다.

바로 비동기 처리가 실패했을때의 값만 받는다.

image.png

사진을 보면 실패한 오류에 대해서 콘솔에 찍힌걸 볼 수 있다.

앞에서 설명했을때 then에서도 두개의 콜백을 받는다고 이야기 했는데

이러한 오류를 then에서 처리할수도 있다.

image.png

이렇게 두번째 콜백에 오류처리할 내용을 작성해둔다면
catch와 동일하게 에러 핸들링이 가능하다.

마지막으로 성공과 실패 같은 프로미스 상태와는 관계없이 무조건 실행되는 finally가 있다.

image.png

finally는 catch 와 같이 한개의 콜백함수를 받는다.

프로미스의 이행상태와는 관계없이 무조건 실행시키려는 내용이 있다면 finally를 사용하는것도 좋겠다.

이와 같이 후속처리 메서드를 사용해서 프로미스의 비동기 작업의 상태관리및 결과값을 처리 할수 있다

promise 객체는 만드는 순간 바로 실행된다.

그럼 우리가 원할때에만 동작하도록 하려면 어떻게 해야할까?

async 를 사용해 함수로 만들거나 이런식으로 코드를 변경해야 한다.

image.png

함수를 실행시켜야만 동작하도록

함수를 실행했을때 프로미스 객체를 리턴하는 방식으로 코드를 작성해야한다

둘다 어떤식으로 결과가 나오는지 확인해보자

image.png

동일한 코드를 작성하는데도 Promise를 사용해서 코드를 구성하면 매우 불편하게 느껴진다.

그만큼 async 에 대해 공부하게되면 promise를 사용하는것보다 코드가 직관적으로 변한다는걸 알 수 있다.

프로미스의 동작원리를 직관적으로 표현한 그림이 있어 가져왔다

image.png
출처 : https://springfall.cc/article/2022-11/easy-promise-async-await

자 프로미스는 후속 처리 메서드가 존재해서 .then으로 후속 처리를 이어 나갈 수 있다

그럼 async는 어떤방식으로 후속처리를 할 수 있을까?

image.png

await는 프로미스의 후속처리를 then으로 처리하던 절차를

동기식 코드로 작성할수 있게 해주는 약속어이다.

image.png

await는 async 내부에서만 사용이 가능한 특징이 있다.

일반 함수에서 사용하면 문법오류가 발생한다.

위 사진에서 일반 함수에 await를 넣고 실행시키자

async 로 만든 함수에서만 사용이 가능하다고 오류가 발생한다.

await 는 async로 만든 함수 내부에서 Promise를 반환하는 함수 앞에 await를 붙이면,

해당 Promise가 이행상태 (fulfilled)로 바뀔 때까지 코드가 기다리게 된다.

Promise가 성공 상태 또는 실패 상태로 바뀌기 전까지는 다음 연산을 시작하지 않는 것이특징이다.

자 그럼 실제로 promise로 작성된 함수를 async await를사용해서 변경해보자

image.png

웹페이지를 만들다 보면 우리가 promise 객체를 직접 만드는 일은 거의 없고

일반적으로 Fetch 함수를 사용해서 api요청등을 보내게 되면 반환값으로 promise 객체를 받게된다.

사진에서도 반환값에 promise 객체라고 표시가 되어있다.

image.png

이 함수를 async /await로 변경시켜보자

일단 콘솔화면에서 어떻게 출력되는지 보자

image.png

이런 형태로 데이터를 받아오는 코드이다.

image.png

위에는 promise와 then을 사용해서 진행되는 비동기 작업의 형태이고

아래 코드는 async와 await를 사용해서 비동기 작업으로 변경한 화면이다

코드가 큰 차이점은 없으나

위에 적혀있는 코드는 코드 작성과 동시에 바로 실행이되는 상태라

async await 처럼 함수를 실행했을때 동작하게 변경하려면

번거롭다는걸 느낄 수 있을것이다.

image.png

변경한 방식으로도 동일하게 동작하는걸 볼 수있다.

한가지 프로미스와 다른점이 있다면 에러 핸들링 부분이다.

.then()으로 이어적는 promise의 경우 에러 핸들링도 .catch를 사용해서 처리하면 되지만

async / await는 다로 핸들링하는게 없기 때문에

try / catch를 사용해서 처리해야한다.

image.png

이렇게 사용해서 에러 핸들링이 가능하다.

자 그럼 응용적으로 한가지만 더 작성해보겠다.

만약 방금 async를 사용해서 만든 getBlogdata 함수를 다른 함수에서 호출해서 사용해야한다면 어떻게 해야할까?

다른함수는 async를 붙여서 만든 함수가 아닌경우다

image.png

예를들면 이러한 상황에서 어떻게 해야할까?

답은 간단하다

앞서 promise에 대해서 설명했듯 해당 코드가 성공한다면 .then을 붙여서 이어나가듯 작성하면 된다.

image.png

이런식으로 작성해 나갈 수 있겠다.

지금까지의 내용을 요약해보자.

  • 동기는 직렬, 비동기는 병렬적인 작업을 말한다.

  • 네트워크나 , 대용량 파일등의 결과 처리 시기를 알 수 없는 작업을 비동기작업으로 처리해야한다.

  • 결과를 기다려야 하는 작업을 비동기로 처리할 수 있다.

  • 비동기 작업은 병렬적으로 일을 처리해 효율적이지만, 흐름에 대한 제어는 동기 코드보다 어렵다.

  • new Promise(…)는 async 함수로 적절하게 변환할 수 있다.

  • async 함수 내에서 Promise 에 대해 await 을 걸어서 작업을 기다릴 수 있다.

  • async와 await를 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있어, 가독성이 좋아지고 에러 처리가 간단해진다


스테픈 2km완료했다

image.png

Coin Marketplace

STEEM 0.27
TRX 0.11
JST 0.030
BTC 67734.06
ETH 3803.47
USDT 1.00
SBD 3.47