JavaScript - 비동기란 무엇인가...

Kim-DaHam·2023년 3월 20일
0

JavaScript

목록 보기
12/18
post-thumbnail

🔥 학습목표

  • 비동기/동기의 차이를 설명할 수 있다.
  • 비동기 함수를 조건에 따라 (부분적으로) 동기적으로 사용할 수 있다.
  • 어떤 경우에 중첩된 콜백이 발생하는지 이해할 수 있다.
  • callback, promise, async/await 의 차이를 설명할 수 있다.



🟩 비동기

🟣 동기와 비동기

⬜ 동기

특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것


⬜ 비동기

특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 수행하는 것


⬜ JavaScript의 작동 원리

JavaScript는 싱글 스레드 기반으로 동작하는 언어이므로 동기적으로 작동한다.

그러나 JavaScript에서는 비동기 처리 또한 가능하다.

왜 그럴까?

그 이유는 JavaScript가 작동하는 환경(런타임)에서 비동기 처리를 도와주기 때문이다.
더 자세한 내용은 후에 학습하고 기록한다.

당장은 특별한 작업 없이 비동기 처리를 할 수 있다는 것만 알고 비동기 코드 작성에 집중하자!


⬜ 비동기 문제점 - 타이머 API

타이머 API는 브라우저에서 제공하는 Web API이며 비동기로 작동하도록 구성되어 있다.

<타이머 관련 API>

setTimeout(callback, millisecond) : 일정 시간 후에 함수를 실행한다.

  • (실행할 콜백 함수, 콜백 함수 실행 전 기다리는 시간)
  • return 값: 임의의 타이머 ID
setTimeout(function () {
  console.log('1초 후 실행');
}, 1000);
// 123

clearTimeout(timerID) : setTimeout 타이머를 종료

  • return 값: 없음
const timer = setTimeout(function () {
  console.log('10초 후 실행');
}, 10000);
clearTimeout(timer);
// setTimeout이 종료됨.

setInterval(callback, millisecond) : 일정 시간의 간격을 가지고 함수를 반복적으로 실행한다.

  • (실행할 콜백 함수, 반복적으로 함수를 실행시키는 시간 간격)
  • return 값: 임의의 타이머 ID
setInterval(function () {
  console.log('1초마다 실행');
}, 1000);
// 345

clearInterval(timerID) : setInterval 타이머를 종료

  • return 값: 없음
const timer = setInterval(function () {
  console.log('1초마다 실행');
}, 1000);
clearInterval(timer);
// setInterval이 종료됨.

그러나 타이머API를 활용하여 코드를 작성하면 문제가 발생한다.

비동기 코드는 코드가 '코드가 작성 된 순서대로 작동'되는 것이 아닌, 동작이 완료되는 순서대로 작동하게 된다. 코드의 순서를 예측할 수가 없다.


node index.js 명령어로 위 코드를 실행시켜 보면 코드를 실행할 때마다 순서가 바뀌는 걸 확인할 수 있다.

개발자는 제어 가능한, 예측 가능한 코드를 작성해야 하는데, 타이머 API와 같이 비동기로 작동하는 코드는 어떻게 제어해야 할까?



🟩 비동기를 동기적으로 구현

비동기로 작동하는 코드를 제어할 수 있는 방법은 대표적으로 세 가지가 있다.

🟣 Callback

⬜ 예시


⬜ 불편한 점

코드가 길어질 수록 복잡해지고 가독성이 낮아지는 Callback Hell이 발생하는 단점이 있다.



🟣 Promise

비동기 코드를 제어할 수 있는 다른 방법으로 Promise 클래스가 있다. Callback Hell을 방지하는 역할을 수행한다.

⬜ 예시

let promise = new Promise((resolve, reject) => {
	// 1. 정상적으로 처리되는 경우
	// resolve의 인자에 값을 전달할 수도 있습니다.
	resolve(value);

	// 2. 에러가 발생하는 경우
	// reject의 인자에 에러메세지를 전달할 수도 있습니다.
	reject(error);
});

프로미스가 정상적으로 처리 된 경우 프로미스 객체는 아래와 같다.

프로미스가 에러가 발생한 경우 프로미스 객체는 아래와 같다.


Promise 객체의 내부 프로퍼티

new Promise가 반환하는 Promise 객체는 state, result 내부 프로퍼티를 갖는다. 두 프로퍼티에 직접 접근은 불가능 하지만, .then, .catch, .finally 메서드를 사용하면 접근이 가능하다.


state

  • pending(대기) : 기본 상태
  • fufilled(이행) : 비동기 처리를 수행할 콜백 함수(excutor)가 성공적으로 작동한 경우
  • rejected(거부) : 에러 발생

result

  • undefined : 처음 상태
  • value : 콜백 함수(excutor)가 성공적으로 작동하여 resolve(value)가 호출된 경우
  • error : 에러가 발생하여 reject(error)가 호출된 경우

⬜ then, catch, finally

then

excutor 에 작성한 코드들이 정상적으로 처리 → resolve 함수 호출 → .then 메서드로 접근

  • .then 안에서 리턴한 값이 PromisePromise 객체의 내부 프로퍼티인 result를 다음 .then의 콜백 함수 인자로 받을 수 있다.
  • Promise가 아니라면 리턴한 값을 .then의 콜백 함수의 인자로 받아올 수 있다.
  • .then, .catch, .finally 메서드는 Promise를 리턴한다.(Promise chaining 가능)
let promise = new Promise((resolve, reject) => {
	resolve("성공");
});

promise.then(value => {
	console.log(value);
	// "성공"
})

catch

executor에 작성했던 코드들이 에러 발생 → → reject 함수를 호출 → .catch 메서드로 접근

let promise = new Promise(function(resolve, reject) {
	reject(new Error("에러"))
});

promise.catch(error => {
	console.log(error);
	// Error: 에러
})

finally

executor에 작성했던 코드들의 정상 처리 여부와 상관없이 .finally 메서드로 접근 가능

let promise = new Promise(function(resolve, reject) {
	resolve("성공");
});

promise
.then(value => {
	console.log(value);
	// "성공"
})
.catch(error => {
	console.log(error);
})
.finally(() => {
	console.log("성공이든 실패든 작동!");
	// "성공이든 실패든 작동!"
})

⬜ Promise.all

여러 개의 비동기 작업을 동시에 처리하고 싶을 때 사용. 인자로 배열을 받는다. 배열에 있는 모든 Promise에서 동작이 정상 처리 된다면 그 결과를 새로운 배열에 저장해 새로운 Promise 객체를 반환한다.

Promise.all([
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러1'))), 1000),
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러2'))), 2000),
	new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러3'))), 3000),
])
	.then((value) => console.log(value))
  .catch((err) => console.log(err));
	// Error: 에러1

⬜ Callback과 차이점

🔴 callback을 사용할 경우, 로직의 결과값을 처리하기 위해선 callback 안에서만 동작이 가능하고, 그 밖에서는 비동기 계산 값을 알 수가 없다.

🔵 promise를 사용할 경우, 비동기 계산 값이 promise 객체에 저장되기 때문에 .then, .catch, .finally와 같은 메서드로 접근이 가능하다.



🟣 async/await

하지만 Promise도 코드가 길어질 수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생한다.

JavaScript ES8은 async/awiat 키워드를 제공하여 이러한 문제를 해결했다.

⬜ 예시

함수 앞에 async 키워드를 사용하고 async 함수 내에서만 await 키워드를 사용한다.

await 키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작한다.

// 함수 선언식
async function funcDeclarations() {
	await 첫 번째 수행 코드
    await 첫 번째 코드가 끝난 뒤 수행되는 코드
	...
}

// 함수 표현식
const funcExpression = async function () {
	await 마찬가지
	...
}

// 화살표 함수
const ArrowFunc = async () => {
	await 마찬가지
	...
}

node index.js 명령어를 실행하면 동기적으로 출력되는 결과를 확인할 수 있다.


⬜ Promise와 차이점

promise.then 메서드를 통해 접근해야만 결과 값을 반환받을 수 있어 코드가 길어질 경우 가독성이 좋지 않았지만,

async/awaitawait를 통해 Promise 반환 값을 바로 받아올 수 있어 코드 가독성이 좋다.



profile
다 하자

0개의 댓글