[1] Keyword
: 동기, 비동기, Promise, Async, Await
# 출처 (아래 출처를 보고 정리한 내용입니다.)
# 동기
- 동시에 여러 작업을 수행할 수 없다.
- 흐름을 예측하기 쉽다.
# 비동기
- 동시에 여러 작업을 수행할 수 있다.
- 흐름을 예측하기 어렵다. (무엇이 먼저 완료될 지 보장 X)
> 의존성이 길게 이어져 있는 비동기 작업들을 처리할 때 곤혹에 치를 수 있음
예시 #1
function finishJob(num) {
console.log(`${num}번 요원의 정보를 받아왔음`);
}
setTimeout(finishJob, 2000, 1);
setTimeout(finishJob, 1500, 2);
setTimeout(finishJob, 1000, 3);
// 결과값
❯ node.exe example.js
3번 요원의 정보를 받아왔음
2번 요원의 정보를 받아왔음
1번 요원의 정보를 받아왔음
그럼 어떤 작업들이 비동기로 수행되는 것인가?
- 브라우저에서는 ajax라 불리는 XMLHttpRequest 객체를 활용하여 비동기적으로 요청을 보내고 받을 수 있음
- Fetch API를 사용
- 파일을 다룰 때 쓰는 함수들
[2] Promise
Promise는 비동기 작업의 단위이다.
1. 기본 사용법
제일 정석적인 방법은 new Promise(...)로 선언하는 것이다.
const promise1 = new Promise((resolve, reject) => {
// 비동기 작업 수행
})
- new Promise(...)로 Promise 객체를 새로 만들었다.
생성자는 함수이므로 괄호()를 써서 함수를 호출하는 것과 동일한 모습이다.
- 생성자를 특별한 함수 하나를 인자로 받는다. (이 특별한 함수를 공식 문서에서 executor라고 부른다.)
- Promise는 new Promise(...) 하는 순간 여기에 할당된 비동기 작업은 바로 시작된다.
⇒ Promise가 끝나고 난 다음의 동작을 설정해줄 수 있는데, then 메서드와 catch 메서드이다.
예시 #2
- new Promise(...) 키워드로 promiseExample이란 Promise 객체를 만들었다.
- Promise는 new Promise(...)하는 순간 비동기 작업은 바로 시작된다.
=> 비동기 작업이기 때문에, 비동기 작업이 끝난 다음의 어떤 작업을 수행할 지 정의할 수 있다.
=> 어떤 작업을 수행할지에 대한 메서드가 바로 then() 메서드와 catch() 메서드이다.
- Callback 함수의 인자 resolve를 실행하면 이행(Fulfilled) 상태가 된다. 이행 상태가 되면 아래와 같이 then() 메서드를 이용하여 처리 결과 값을 받을 수 있다.
- 콜백 함수 인자로 reject도 사용할 수 있는데, reject를 호출하면 실패(Rejected)로 간주되어 catch() 메서드를 이용하여 에러 결과를 받을 수 있게 된다.
const promiseExample = new Promise((resolve, reject) => {
resolve();
});
promiseExample
.then(() => {
console.log("This is then.");
})
.catch(() => {
console.log("This is catch.");
});
// 결과값
❯ node.exe example.js
This is then.
예제 #3
checkAge 함수를 호출할 때 인자 age에 따라 resolve(이행)를 수행할 것인지 reject(거부)를 수행할 것인지 정의할 수 있다.
function checkAge(age) {
return new Promise((resolve, reject) => {
if (age > 20) resolve();
else reject();
});
}
setTimeout(() => {
const promise1 = checkAge(25);
promise1
.then(() => {
console.log("promise1 then.");
})
.catch(() => {
console.log("promise1 catch");
});
const promise2 = checkAge(15);
promise2
.then(() => {
console.log("promise2 then.");
})
.catch(() => {
console.log("promise2 catch.");
});
}, 1000);
// 결과값
**❯ node example.js**
promise1 then.
promise2 catch.
예제 #4
resolve, reject 함수에 인자를 전달함으로 then 및 catch 함수에서 비동기 작업으로부터 정보를 얻을 수 있음
function startAsync(age) {
return new Promise((resolve, reject) => {
if (age > 20) resolve(`${age} success`);
else reject(new Error(`${age} is not over 20.`));
});
}
setTimeout(() => {
const promise1 = startAsync(25); // 조건에 부합하므로 resolve
promise1
.then((value) => {
console.log(value);
})
.catch((error) => {
console.error(error);
});
const promise2 = startAsync(15);
promise2
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
});
}, 1000);
간단한 원리를 그림으로 나타내기
# 요약
⇒ Promise는 비동기 작업을 생성/시작하는 부분(new Promise(...))과 작업 이후의 동작 지정 부분(then, catch)을 분리함으로써 기존의 러프한 비동기 작업보다 유연한 설계를 가능토록 한다.
⇒ Promise를 만드는 순간 비동기 작업이 시작되며, 비동기 작업을 성공으로 간주하고 싶을 경우 resolve를 호출하고, 실패로 간주하고 싶다면 reject 함수를 호출한다.
⇒ 비동기 작업이 성공했을 때의 후속 조치를 지정하고 싶다면 then으로, 실패 시의 후속 조치는 catch로 지정해주면 된다.
[3] Async
: 비동기 작업을 만드는 손쉬운 방법
async 함수는 Promise와 밀접한 연관이 있는데, 기존에 작성하던 executor로부터 몇 가지 규칙만 적용한다면 new Promise(...)를 리턴하는 함수를 async 함수로 손쉽게 변환할 수 있다.
⇒ 함수에 async 키워드를 붙임
⇒ new Promise ... 부분을 없애고 executor 본문 내용만 남김
⇒ resolve(value); 부분을 return value; 로 변경
⇒ reject(new Error(...)); 부분을 throw new Error(...); 로 수정한다.
예제 #5
async function startAsync(age) {
if (age > 20) return `${age} success`;
else throw new Error(`${age} is not over 20`);
}
setTimeout(() => {
const promise1 = startAsync(25);
promise1
.then((value) => {
console.log(value);
})
.catch((error) => {
console.error(error);
});
const promise2 = startAsync(15);
promise2
.then((value) => {
console.log(value);
})
.catch((error) => {
console.error(error);
});
}, 1000);
❯ node example.js
25 success
Error: 15 is not over 20
at startAsync (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:3:16)
at Timeout._onTimeout (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:17:22)
at listOnTimeout (node:internal/timers:557:17)
at processTimers (node:internal/timers:500:7)
위의 코드로부터 알 수 있는 사실
- async 함수의 리턴 값은 무조건 Promise 이다.
⇒ promise1, promise2 객체는 async 함수를 실행시킨 뒤 then과 catch를 활용하여 흐름을 제어.
- async 함수 안에서는 await 키워드를 사용할 수 있다.
await : Promise가 끝날 때까지 기다리기
await은 Promise가 이행되었든 거부되었든간에 끝날 때까지 기다리는 함수이다.
await의 제약 조건 : async 함수에서만 사용할 수 있다.
예제 #6
function setTimeoutPromise(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), ms);
});
}
async function startAsync(age) {
if (age > 20) return `${age} success`;
else throw new Error(`${age} is not over 20`);
}
async function startAsyncJobs() {
await setTimeoutPromise(1000);
const promise1 = startAsync(25);
try {
const value = await promise1;
console.log(value);
} catch (e) {
console.error(e);
}
const promise2 = startAsync(15);
try {
const value = await promise2;
console.log(value);
} catch (e) {
console.error(e);
}
}
startAsyncJobs();
// Jobs
❯ node example.js
25 success
Error: 15 is not over 20
at startAsync (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:9:16)
at startAsyncJobs (/mnt/c/Users/User/Documents/git/JavaScript/비동기/example.js:24:22)
1. await은 Promise가 완료될 때까지 기다린다.
⇒ setTimeoutPromise의 executor에서 resolve 함수가 호출될 때까지 기다린다.
2. await은 Promise가 resolve한 값을 내놓는다.
⇒ async 함수 내부에서는 리턴하는 값을 resolve 한 값으로 간주하므로, value로 ${age} success가 들어오게 된다.
3. 해당 Promise 구문에서 reject가 발생한다면 예외처리가 발생한다.
⇒ 이 예외를 처리하기 위해 try-catch 구문이 사용된다.
await은 then과 catch의 동작을 모두 자기 나름대로 처리한다. 그래서 async 함수 내에서 then, catch 메서드의 존재를 잊게 할 수 있다.
# Promise.all : 여러 비동기 동작을 한꺼번에 기다리기
Promise.all : 인자로 Promise의 배열을 받으며, 하나의 특별한 Promise를 새로 생성한다.
이 Promise 배열로 받은 모든 비동기 작업이 성공했다면 내부적으로 resolve를 호출하며, 하나라도 비동기 작업이 실패한다면 reject를 호출한다.
예제 #7
function setTimeoutPromise(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), ms);
});
}
async function fetchAge(id) {
await setTimeoutPromise(1000);
console.log(`${id} 사원 데이터 받아오기 완료!`);
return parseInt(Math.random() * 20, 10) + 25;
}
async function startAsyncJobs() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(fetchAge(i));
}
let ages = await Promise.all(promises);
console.log(`평균 나이는? ==> ${ages.reduce((prev, current) => prev + current, 0) / ages.length}`);
}
startAsyncJobs();
// 결과값
❯ node example.js
0 사원 데이터 받아오기 완료!
1 사원 데이터 받아오기 완료!
2 사원 데이터 받아오기 완료!
3 사원 데이터 받아오기 완료!
4 사원 데이터 받아오기 완료!
5 사원 데이터 받아오기 완료!
6 사원 데이터 받아오기 완료!
7 사원 데이터 받아오기 완료!
8 사원 데이터 받아오기 완료!
9 사원 데이터 받아오기 완료!
평균 나이는? ==> 36.7
[4] Async와 Promise 요약 정리
- Promise는 JavaScript에서 비동기 처리에 사용되는 객체이며 resolve와 reject를 통해 콜백의 성공/실패 케이스를 작성할 수 있다.
- Await은 Promise 기반으로 동작한다. 비동기 코드를 async, await을 통해 가독성을 높여주며 예외 처리는 try - catch를 통해 수행할 수 있다.
- DB로부터 데이터를 가져와야 하는데 사용자에게는 지속적으로 서비스를 제공해야 할 경우 특정 메서드를 비동기로 가져와야 하기 때문에 이럴 때 async - await 구문을 사용할 수 있다고 보면 된다.
에제 #8 - Promise
처음 then에서 resolve하여 다음 then에서 인자로 받도록 하기 위해 아래와 같이 Code를 구현할 수 있다.
const condition = true;
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve('success');
} else {
reject("fail");
}
});
promise
.then((message) => {
console.log(`첫 번째 then 이며, message : ${message}를 전달 받았습니다.`);
return new Promise((resolve, reject) => {
resolve(message);
});
})
.then((message2) => {
console.log(`두 번째 then이며, mssage2 : ${message2}를 전달 받았습니다.`);
return new Promise((resolve, reject) => {
resolve(message2);
});
})
.then((message3) => {
console.log(`세 번째 then이며, message3 : ${message3}를 전달 받았습니다.`);
})
.catch((error) => {
console.error(`error 내용 : ${error}`);
});
**// 결과값
❯ node 비동기/example.js
첫 번째 then 이며, message : success를 전달 받았습니다.
두 번째 then이며, mssage2 : success를 전달 받았습니다.
세 번째 then이며, message3 : success를 전달 받았습니다.**
예제 #9 - Async, await
user 객체를 DB로부터 가져오기 위해서 async - await 구문을 통해 비동기를 구현할 수 있다.
async function findAndSaveUser(Users) {
let user = await Users.findOne({});
user.name = 'zero';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
// 생략
}
예제 #10 - Async, await 예외 처리
try - catch로 예외 처리를 수행할 수 있다.
async function findAndSaveUser(Users) {
try {
let user = await Users.findOne({});
user.name = 'zero';
user = await user.save();
user = await Users.findOne({ gender: 'm' });
// 생략
} catch (error) {
console.error(error);
}
}
## 출처
'Programming > Javascript' 카테고리의 다른 글
[Javascript] 반복문 정리 (0) | 2021.11.14 |
---|