Jieunny의 블로그
S2) Unit 3. [JS] 비동기(Callback, Promise, Async/Await) 본문
S2) Unit 3. [JS] 비동기(Callback, Promise, Async/Await)
Jieunny 2023. 1. 17. 12:00✔️ Blocking : 하나의 작업이 끝날 때까지 이어지는 작업을 막는 것
✔️ 동기적 : 시작 시점과 완료 시점이 같은 상황
📌 non-blocking 하고 비동기적으로 작동하는 것이 효율적
📣 자바스크립트의 동기 & 비동기
✔️ 동기 : 특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것
✔️ 비동기 : 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드들을 수행하는 것
✔️ JS의 작동원리
JS는 싱글 스레드 기반 언어(스레드가 하나라서 동시에 작업이 불가능하다 -> 동기적으로 작동한다)
자바스크립트 런타임에서 비동기 처리를 도와주기 때문에 특별한 작업 없이 비동기 처리를 할 수 있다.
📣 비동기 JS
✔️ 타이머 관련 API
1️⃣ setTimeout(callback, millisecond)
➰ 일정 시간 후에 함수를 실행한다.
➰ 매개변수 : 실행 할 콜백함수, 콜백 함수 실행 전 기다려야 할 시간(밀리초)
➰ 임의의 타이머 ID를 리턴
setTimeout(function () {
console.log('1초 후 실행');
}, 1000);
// 123
➰ console.log는 함수 호출 후 1초 후에 실행된다.
2️⃣ clearTimeout(timerId)
➰ setTimeout 타이머를 종료한다.
➰ 매개변수 : 타이머 ID
➰ 리턴 X
const timer = setTimeout(function () {
console.log('10초 후 실행');
}, 10000);
clearTimeout(timer);
// setTimeout이 종료됨.
3️⃣ setInterval(callback, millisecond)
➰ 일정 시간의 간격을 가지고 함수를 반복적으로 실행한다.
➰ 매개변수 : 실행 할 콜백 함수, 함수를 반복적으로 실행시키기 위한 시간 간격(밀리초)
➰ 임의의 타이머 ID를 리턴
setInterval(function () {
console.log('1초마다 실행');
}, 1000);
// 345
➰ console.log 는 10초마다 실행된다.
4️⃣ clearInterval(timerId)
➰ setInterval 타이머를 종료한다.
➰ 매개변수 : 타이머 ID
➰ 리턴 X
const timer = setInterval(function () {
console.log('1초마다 실행');
}, 1000);
clearInterval(timer);
// setInterval이 종료됨.
🚨 항상 개발자가 제어할 수 있는 코드를 작성하자
📣 비동기 코드의 순서를 제어할 수 있는 방법
1️⃣ Callback()
✔️ 비동기 코드의 순서를 제어할 수 있는 방법 (비동기를 동기화 할 수 있다)
const printString = (string, callback) => {
setTimeout(function () {
console.log(string);
callback();
}, Math.floor(Math.random() * 100) + 1);
};
const printAll = () => {
printString('A', () => {
printString('B', () => {
printString('C', () => {});
});
});
};
// A B C
➰ callback()이 없다면 무작위로 시간을 설정했기 때문에 매번 다른 결과가 나올 것이다.
➰ 랜덤한 시간이 지나면 setTimeout() 이 실행되고, 전달받은 문자열을 출력한 뒤 callback() 인자로 전달받은 함수(다음 문자열 찍는 함수)를 실행하게 된다.
🚨 이렇게 하면 비동기가 아니라 동기적으로 작동하는 게 아닌가?
콘솔에 찍는 순서로만 보면 동기적으로 보일 수 있지만, 콘솔이 아닌 어떤 작업을 하는 코드 였다면 작업을 종료한 뒤 행동(콘솔로 찍는 것)만 순차적으로 하게 되는 것이므로
비동기 적으로 작동하는 게 맞다.
🚨 Callbak Hell : callback 함수를 사용하면 비동기 코드의 순서를 제어할 수 있지만 코드가 길어질 수록 복잡하고 가독성이 낮아지는 단점 -> 이를 방지하기 위해 Promise 가 사용되기 시작했다.
2️⃣ Promise
✔️ class 이므로 new 키워드를 통해 Promise 객체를 생성한다.
✔️ 비동기 처리를 진행할 콜백 함수(excutor)를 전달받는데, 이 콜백 함수는 resolve, reject 함수를 인수로 전달받는다.
✔️ 객체가 생성되면 excutor는 자동으로 실행되고, 코드가 정상적으로 실행되면 resolve 함수를, 에러가 발생하면 reject 함수를 호출한다.
let promise = new Promise((resolve, reject) => {
// 1. 정상적으로 처리되는 경우
// resolve의 인자에 값을 전달할 수도 있습니다.
resolve(value);
// 2. 에러가 발생하는 경우
// reject의 인자에 에러메세지를 전달할 수도 있습니다.
reject(error);
});
✔️ Promise 객체의 내부 프로퍼티
➰ Promise 객체는 state, result 내부 프로퍼티를 갖는다.
➰ 직접 접근은 할 수 없고 .then, .catch, .finally 메서드를 사용해야 접근 할 수 있다.
➕ State, Result
✔️ State
➰ 기본 상태는 pending(대기) 이다.
➰ 콜백 함수가 성공적으로 작동했다면 fulfilled(이행) 로 변경되고, 에러가 발생했다면 rejected(거부) 된다.
✔️ Result
➰ 처음에는 undefined 이다.
➰ 콜백 함수가 성공적으로 작동해서 resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변한다.
➕ Then, Catch, Finally
✔️ 모두 Promise 객체를 반환한다.
✔️ Then
➰ executor 코드들이 정상적으로 처리되면 resolve 함수를 호출하고, .then으로 접근 가능하다.
➰ .then 안에서 리턴한 값이 Promise라면 result를 다음 .then의 콜백 함수의 인자로 받아오고, Promise가 아니라면 리턴한 값을 현재 .then의 콜백 함수의 인자로 받아올 수 있다.
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); // "성공" executor가 성공했을 경우 실행된다. }) .catch(error => { console.log(error); }) // executor에서 에러가 발생했을 경우 실행된다. .finally(() => { console.log("성공이든 실패든 작동!"); // "성공이든 실패든 작동!" })
2️⃣−1️⃣ Promise chaining
✔️ 비동기 작업을 순차적으로 진행해야 하는 경우 사용
✔️ .then, .catch, .finally 메서드는 모두 Promise를 반환하기 때문에
성공할 경우 .then을 통해 계속 연결할 수 있고, 에러가 발생하면 .catch로 처리가 가능하다.
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(string);
}, Math.floor(Math.random() * 100) + 1);
});
};
const printAll = () => {
printString('A')
.then(() => { // 'A'를 출력하는 게 성공하면
return printString('B'); // 'B'를 출력하고,
})
.then(() => { // 'B'를 출력하는 게 성공하면
return printString('C'); //'C'를 출력한다.
});
};
printAll();
console.log(
`아래와 같이 Promise를 통해 비동기 코드의 순서를 제어할 수 있습니다!`
);
//아래와 같이 Promise를 통해 비동기 코드의 순서를 제어할 수 있습니다!
//A
//B
//C
let promise = new Promise(function (resolve, reject) {
resolve('성공');
// reject("실패");
});
promise
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.catch((error) => {
console.log(error);
return '실패';
})
.finally(() => {
console.log('성공이든 실패든 작동!');
});
//성공
//성공
//성공
//성공이든 실패든 작동!
2️⃣−2️⃣ Promise.all()
✔️ 여러 개의 비동기 작업을 동시에 처리하고 싶을때 사용한다.
✔️ 인자로는 배열을 받고, 해당 배열에 있는 모든 Promise에서 executor 내 작성했던 코드들이 성공하면 결과를 배열에 저장해 새로운 Promise를 반환 해준다.
const promiseOne = () => new Promise((resolve, reject) => setTimeout(() => resolve('1초'), 1000));
const promiseTwo = () => new Promise((resolve, reject) => setTimeout(() => resolve('2초'), 2000));
const promiseThree = () => new Promise((resolve, reject) => setTimeout(() => resolve('3초'), 3000));
// promise.all
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
.then((value) => console.log(value))
// ['1초', '2초', '3초']
.catch((err) => console.log(err));
// 하나라도 에러가 발생했을 경우
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
➰ 인자로 받는 배열에 있는 Promise 중 하나라도 에러가 발생하게 되면 나머지 Promise의 state와 상관없이 즉시 종료된다.
2️⃣−3️⃣ Promise Hell
✔️ callback 함수와 마찬가지로 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell 이 발생할 수 있다.
3️⃣ Async / Await
✔️ ES8 부터 제공하는 키워드로, 복잡한 Promise 코드를 간결하게 작성할 수 있다.
✔️ 함수 앞에 async 키워드를 사용하고, async 함수 내에서만 await 키워드를 사용하면 된다.
✔️ await 키워드가 작성된 코드가 동작하고 나서, 다음 순서의 코드가 동작하게 된다.
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(string);
}, Math.floor(Math.random() * 100) + 1);
});
};
const printAll = async () => {
await printString('A');
await printString('B');
await printString('C');
};
printAll();
console.log(
`Async/Await을 통해 Promise를 간결한 코드로 작성할 수 있게 되었습니다.`
);
'CodeStates > learning contents' 카테고리의 다른 글
S2) Unit 3. [JS] fetch & Axios (0) | 2023.01.19 |
---|---|
S2) Unit 3. [JS] Node.js (0) | 2023.01.18 |
S2) Unit 2. [JS] 프로토타입 (0) | 2023.01.13 |
S2) Unit 2. [JS] 객체 지향 프로그래밍 (0) | 2023.01.13 |
S2) Unit 2. [JS] 클래스와 인스턴스 (0) | 2023.01.13 |