[JS] 비동기 이해하기

강경서·2023년 10월 27일
0
post-thumbnail

비동기란 무엇일까?

자바스크립트에서 작업을 수행하는 방법으로는 동기(Synchronous)비동기(Asynchronous) 로 불리는 2가지 방식이 있습니다.


동기(Synchronous)

자바스크립트는 싱글 스레드 언어 이기 때문에 한 번에 하나의 작업만 수행할 수 있습니다. 이는 이전 작업이 완료되어야 다음 작업을 수행하는 동기(Synchronous) 방식의 수행 방법입니다.


비동기(Asynchronous)

하지만 위와 같은 동기 방식은 작업이 오래 걸리거나 응답이 늦어지는 경우에는 전체적인 성능과 사용자 경험에 영향을 줄 수 있습니다. 예를 들어 서버에 데이터를 요청하고 응답을 박아야 하는 작업이 있다면, 응답이 올 때까지 다른 작업을 하지 못하고 대기해야만 합니다. 이렇게 되면 프로그램은 흐름이 멈추거나 지연되게 됩니다.

따라서 자바스크립트로 여러 작업을 동시에 처리하기 위해 비동기(Asynchronous) 라는 개념을 도입하여, 특정 작업의 완료를 기다리지 않고 다른 작업을 동시에 수행할 수 있도록 하였습니다. 비동기는 메인 스레드가 작업을 다른 곳에 인가하여 처리되게 하고, 그 작업이 완료되면 콜백 함수를 받아 실행하는 방식으로, 쉽게 말해 작업을 백그라운드에 요청하여 처리되게 하여 멀티로 작업을 동시에 처리하는 것으로 보면 됩니다.

싱글 스레드 언어가 비동기 방식이 가능한 이유?
자바스크립트는 분명 싱글 스레드 언어이지만 브라우저에서 별도의 API를 사용하여 동시에 작업을 처리하는 비동기 방식이 가능합니다. 자바스크립트는 실행되는 함수를 싱글 스레드 형식으로 처리하는 메인 콜 스택이 있고 자바스크립트의 이벤트 루프기능이 콜 스택 내에서 현재 실행중인 작업이 있는지 확인하고 실행합니다. 이떄 비동기 방식의 작업이 호출되면 해당 작업은 콜 스택에서 브라우저에 내장된 Web API에게 넘겨집니다. Web API 안에서 작업의 조건이 완료되어 실행될 준비가 되면 콜백 큐에서 해당 작업은 대기하게 됩니다. 이벤트 루프콜백 큐에 작업이 대기하고 있다면 콜 스택이 비어있을 떄 콜백 큐에 있던 해당 작업을 콜 스택으로 이동시키고 실행합니다.


비동기 처리하기

비동기 방식을 이용할때 주의해야 할 점이 있습니다. 비동기 방식은 요청한 작업의 완료 여부를 기다리지 않고 자신의 그다음 작업을 계속 수행합니다. 하지만 그다음 실행할 작업이 이전에 요청한 작업의 결과가 반드시 필요할 경우에 문제가 생기게됩니다. 이러한 문제점을 해결하기 위한 비동기 처리방식이 몇가지 있습니다.


콜백 함수(Callback Function)

콜백 함수는 함수의 매개변수에 함수 자체를 넘겨, 함수 내에서 매개변수 함수를 실행하는 기법을 말합니다. 비동기 방식은 요청과 응답의 순서를 보장하지 않습니다. 따라서 응답의 처리 결과에 의존하는 경우에는 콜백 함수를 이용하여 작업 순서를 간접적으로 끼워 맞출 수 있습니다.

function mainFunction(callback) {
    setTimeout(() => {
        const value = 100;
        callback(value);
    }, 3000);
}

다만 너무 복잡하게 얽힌 비동기 처리 때문에 콜백 함수 방식은 코드 복잡도를 증가시켜, 개발자가 어플리케이션의 흐름을 읽기 어려워지는 등의 문제가 있을 수 있어 잘못하면 콜백 지옥(callback hell) 에 빠질수 있다는 단점이 있습니다.


프로미스 객체(Promise)

자바스크립트의 Promise 객체는 비동기 처리를 위한 전용 객체입니다. Promise는 비동기 작업의 성공 또는 실패와 그 결과값을 나타내는 객체입니다.

Promise의 상태 (resolve, reject)

Promise는 작업이 진행중인지, 성공인지 아니면 실패했는지에 따라 3가지의 상태를 갖습니다.

  • Pending(대기) : 진행 상태, Promise 객채가 생성되어 사용될 준비가 된 상태입니다. Promise의 객체는 new Promise()로 생성할 수 있으며, 콜백 함수로 resolve, reject를 선언할 수 있습니다.
  • Fulfilled(이행) : 성공 상태, 비동기 처리 후 결과를 정상적으로 처리할 경우입니다. resolve가 호출됩니다.
  • Rejected(거부) : 실패 상태, 에러가 발생될 경우입니다. reject가 호출됩니다.
new Promise((resolve, reject) => {
		getData(
			// 성공 상태
			resolve(response.data),
			// 실패 상태
			reject(error.messase)                
		)
	}
)

Promise 메서드 체인 (then, catch, finally)

체이닝(chaining)이란 동일한 객체에 메서드를 연결하는 방법입니다.

  • then : resolve(성공)시에 then 메서드에 실행할 콜백 함수를 인자로 넘겨줍니다.
  • catch : reject(실패)시에 catch 메서드에 실행할 콜백 함수를 인자로 넘겨줍니다.
  • finally : 성공 또는 실패 여부와 상관없이 모두 실행할 경우에 finally 메서드에 실행할 콜백 함수를 인자로 넘겨줍니다.
function getData() {
  return new Promise(function(resolve, reject) {
    // 데이터 요청
	if (response) {
		resolve(response);
	}
	reject(new Error("Request is failed"));
  });
}

function main() {
	getData().then((data) => {
		console.log(data) // response 출력
	}).catch((err) => {
		console.error(err); // error 출력
	});
}

main()

async/await

async/await는기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와줍니다. 특히 복잡했던 Promise를 조금 더 편하게 사용할 수 있습니다.

async

async는 항상 Promise 객체를 반환합니다. async 키워드는 function 앞에 사용합니다. function 앞에 async를 붙이면 해당 함수는 항상 프라미스를 반환합니다. 프라미스가 아닌 값을 반환하더라도 이행 상태의 프라미스(resolved promise)로 값을 감싸 이행된 프라미스가 반환되도록 합니다.

await

await는 반드시 async 함수 안에서 실행됩니다. then이 하던 작업을 await가 대신합니다. 따라서 await 키워드는, then을 체이닝한 것처럼 순서대로 동작합니다.

function getData() {
  return new Promise(function(resolve, reject) {
    // 데이터 요청
	if (response) {
		resolve(response);
	}
	reject(new Error("Request is failed"));
  });
}

async function main() {
	const data = await getData() // await 키워드로 Promise가 완료될 때까지 기다립니다. 
    console.log(data)
}

main()

async await 에러 제어

try-catch 구문을 사용하여 에러 처리를 할 수 있습니다.

async function asyncFunc() {
  try { 
	const data = await getData()
	console.log(data)
  } catch (err) {  
    console.log(err)
  }
}

🧾 Reference

profile
기록하고 배우고 시도하고

0개의 댓글