‘비동기(Asynchronous)'란?
- 서버로 보낸 요청에 서버가 응답할 때까지 기다리지 않고, 다른 처리를 할 수 있는 행위
- 콜백 지옥을 Promise의 등장으로 해결할 수 있게 되었다.
1-1. Promise
- 미래의 어떤 시점에 결과를 제공하겠다는 ‘약속’ 반환
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
myPromise.then(n => {
console.log(n);
});
작업이 끝나고 나서 또 다른 작업을 해야 할 때에는 Promise 뒤에 .then(...) 을 붙여서 사용하면 된다.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error());
}, 1000);
});
myPromise
.then(n => {
console.log(n);
})
.catch(error => {
console.log(error);
});
// 1초 뒤에 실패되는 함수
- 실패하는 상황에서는 reject 를 사용하고, .catch 를 통하여 실패했을시 수행 할 작업을 설정한다.
- then 내부에 넣은 함수에서 또 Promise 를 리턴하게 된다면, 연달아서 사용 할 수 있다.
function increaseAndPrint(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const value = n + 1;
if (value === 5) {
const error = new Error();
error.name = 'ValueIsFiveError';
reject(error);
return;
}
console.log(value);
resolve(value);
}, 1000);
});
}
increaseAndPrint(0)
.then(n => {
return increaseAndPrint(n);
})
.then(n => {
return increaseAndPrint(n);
})
.then(n => {
return increaseAndPrint(n);
})
.then(n => {
return increaseAndPrint(n);
})
.then(n => {
return increaseAndPrint(n);
})
.catch(e => {
console.error(e);
});
// 혹은 다음과 같은 표현으로 가능함
// increaseAndPrint(0)
// .then(increaseAndPrint)
// .then(increaseAndPrint)
// .then(increaseAndPrint)
// .then(increaseAndPrint)
// .then(increaseAndPrint)
// .catch(e => {
// console.error(e);
- create().then(성공 시 실행 함수, 실패 시 실행 함수)
- create() 함수에서 Promise 객체 인스턴스 생성 → 인스턴스 + executer(실행자)
- then은 코드 끝까지 확인한 후 실행한다.
- 소스 코드 끝까지 내려갔다 다시 올라온다.
- [[promiseState]] : 코드 실행시 파라미터로 반환된 값 저장
- Pending(대기) → settled(이행) || rejected(거부)
- Pending
- Promise 인스턴스 생성 → execute 설정 + 성공, 실패 핸들러 함수 바인딩 (함수 호출 X 환경 셋팅만)
- Settled
- 성공 및 실패 여부를 알 수 있다.
- 성공(fulfill)
- 실패(reject)
- Pending
- Pending(대기) → settled(이행) || rejected(거부)
Promise 인스턴스 특징
형태new Promise
파라미터 | executer, function(resolve, reject) {} |
반환 | Promise (생성한 promise 인스턴스) |
- then() 성공/실패 핸들러 함수의 파라미터는 각 1개
- 여러 개일 경우 배열 사용
- 성공, 실패 핸들러 함수
- return 문의 존재와 무관하게 promise 인스턴스 반환
- [[promiseValue]]에 설정
- return문 작성 O : Promise Value
- return문 작성 X : undefined
- [[promiseValue]]에 설정
- return 문의 존재와 무관하게 promise 인스턴스 반환
.then() 연속
- then(one)의 [[PromiseValue]] 값이 .then(two)의 two 인자값으로 설정
Catch() 실패 핸들러
- catch() 다음에도 then() 연결 가능
- catch()의 [[PromiseValue]]값을 then 인자값으로 여전히 보낸다.
- create() 작성시 (Promise 객체 인스턴스 만드는 함수)
- Promise의 2번째 인자값인 reject() 없으면 catch() 핸들러 함수 호출하지 않는다.
All()
- 모두 성공이었을 때 resolve() 성공 핸들러 함수 실행
- all() 파라미터에 iterable Object
- Promise 인스턴스 하나라도 실패하면 then() 핸들러 실행 X
Promise 단점 → async/await이 ES8에 등장
- 에러를 잡을 때 몇번째에서 발생했는지 알아내기 어렵다.
- 특정 조건에 따라 분기를 나누는 작업도 어렵다.
- 특정 값을 공유해가면서 작업을 처리하기도 까다롭다.
1-2. async-await
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// Expected output: "resolved"
}
asyncCall();
- 목적
- 여러 promise의 동작을 동기스럽게 사용할 수 있게 하고, 어떠한 동작을 여러 promise의 그룹에서 간단하게 동작하게 하는 것
- promise가 구조화된 callback과 유사한 것 처럼 async/await 또한 제네레이터(generator)와 프로미스(promise)를 묶는것과 유사함.
- 장점
- await는 promise.then보다 좀 더 세련되게 프라미스의 result 값을 얻을 수 있도록 해주는 문법
async function foo() {
return 1;
}
function foo() {
return Promise.resolve(1);
}
- 상기 두 코드는 같은 결과를 수행하는 코드
async를 활용한 promise chain
- Promise를 사용한 chain
function getProcessedData(url) {
return downloadData(url) // returns a promise
.catch((e) => {
return downloadFallbackData(url); // returns a promise
})
.then((v) => {
return processDataInWorker(v); // returns a promise
});
}
- async 함수를 사용한 chain
async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch (e) {
v = await downloadFallbackData(url);
}
return processDataInWorker(v);
}
→ return 구문에 await 구문이 없다. async function 반환값이 암묵적으로 Promise.resolve로 감싸지기 때문.
await 연산자
- Promise를 기다리기 위해 사용
- async function 내부에서만 사용 가능
await 문은 Promise가 fulfill되거나 reject 될 때까지 async 함수의 실행을 일시 정지하고, Promise가 fulfill되면 async 함수를 일시 정지한 부분부터 실행한다.
이때 await 문의 반환값은 Promise 에서 fulfill된 값이 된다.
Promise가 reject되면, await 문은 reject된 값을 throw한다.
- await 특징
- 최상위 레벨 코드에서 작동하지 않음
- 만일 작동을 원한다면 익명 async 함수로 감싸면 가능
function resolveAfter2Seconds(x) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
- 만약 Promise가 await에 넘겨지면, await은 Promise가 fulfill되기를 기다렸다가, 해당 값을 리턴
await 에러 핸들링
- 단순 구조
async function f() {
await Promise.reject(new Error("에러 발생!"));
}
// 혹은
async function f() {
throw new Error("에러 발생!");
}
- 복수개의 흐름을 제어해야 할 때
async function f() {
try {
let response = await fetch('http://유효하지-않은-주소');
let user = await response.json();
} catch(err) {
// fetch와 response.json에서 발행한 에러 모두를 여기서 잡습니다.
alert(err);
}
}
f();
async-await 사용 예시
- 사용 방법
- async가 붙은 함수는 결과값으로 Promise 를 반환
- async가 붙은 함수 내에서만 await 연산을 사용할 수 있다.
- Promise.resolve(1) 대신return 1; 만 해도 된다.
- 기본 예시
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)
alert(result); // "완료!"
}
f();
- async 함수에서 에러를 발생 시킬 때와 에러를 잡아낼 때
- 에러 발생 시킬 때는 throw를 사용하고, 에러를 잡을 때에는 try/catch 를 사용한다.
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function makeError() {
await sleep(1000);
const error = new Error();
throw error;
}
async function process() {
try {
await makeError();
} catch (e) {
console.error(e);
}
}
process();
- 배열 비구조화 할당 (구조 분해 할당) 문법 사용도 가능
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const getDog = async () => {
await sleep(1000);
return '멍멍이';
};
const getRabbit = async () => {
await sleep(500);
return '토끼';
};
const getTurtle = async () => {
await sleep(3000);
return '거북이';
};
async function process() {
const [dog, rabbit, turtle] = await Promise.all([
getDog(),
getRabbit(),
getTurtle()
]);
console.log(dog);
console.log(rabbit);
console.log(turtle);
}
process();
Promise Chaining
- Promise를 활용한 loadJson
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
})
}
loadJson('no-such-user.json')
.catch(alert); // Error: 404
- async/await을 사용한 loadJson
async function loadJson(url) { // (1)
let response = await fetch(url); // (2)
if (response.status == 200) {
let json = await response.json(); // (3)
return json;
}
/* 혹은 */
if (response.status == 200) {
return response.json(); // (3)
} // 대신, 프라미스가 이행되는 것을 await을 통해 바깥 코드에서 기다려야 한다.
throw new Error(response.status);
}
loadJson('no-such-user.json')
.catch(alert); // Error: 404 (4)
async/await vs promise.then/catch
async/await의 장단점
async/await을 사용하면 await가 대기를 처리해주기 때문에 .then이 거의 필요하지 않다.
여기에 더하여 .catch 대신 일반 try..catch를 사용할 수 있다는 장점도 생긴다.
항상 그러한 것은 아니지만, promise.then을 사용하는 것보다 async/await를 사용하는 것이 대개 더 편리.
그런데 문법 제약 때문에 async함수 바깥의 최상위 레벨 코드에선 await를 사용할 수 없다. 그렇기 때문에 관행처럼 .then/catch를 추가해 최종 결과나 처리되지 못한 에러를 다룬다.
Promiss.all()
async/await는 Promise.all과도 함께 쓸 수 있다.
여러 개의 프라미스가 모두 처리되길 기다려야 하는 상황이라면 이 프라미스들을 Promise.all로 감싸고 여기에 await를 붙여 사용할 수 있습니다.
-
// 프라미스 처리 결과가 담긴 배열을 기다립니다. let results = await Promise.all([ fetch(url1), fetch(url2), ... ]);
실패한 프라미스에서 발생한 에러는 보통 에러와 마찬가지로 Promise.all로 전파됩니다. 에러 때문에 생긴 예외는 try..catch로 감싸 잡을 수 있습니다.
간단한 Timer 예제.tsx
- Promise와 Async-await의 문법 차이 및 혼용해서 사용하는 경우
"use client";
export function promiseTimer(time: number) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(time);
}, time);
});
}
export default function Asyncawait() {
const onPromiseTimer = () => {
console.log("start");
promiseTimer(1000)
.then(function (time: any) {
console.log("time:" + time);
return promiseTimer(time + 1000);
})
.then(function (time: any) {
console.log("time:" + time);
return promiseTimer(time + 1000);
})
.then(function (time: any) {
console.log("time:" + time);
//return promiseTimer(time + 1000);
console.log("end");
});
//console.log("end"); // start, end가 바로 나온다. timer는 비동기기 때문에 보내고 자기 일 하는 것.
};
const onAsyncTimer = async () => {
console.log("start");
let time: any = await promiseTimer(1000);
console.log("time:" + time);
time = await promiseTimer(time + 1000);
console.log("time:" + time);
time = await promiseTimer(time + 1000);
console.log("time:" + time);
console.log("end");
};
// console.log('parent start');
// onAsyncTimer() -> 현재에는 button 클릭시 실행이지만 자동 실행하려면 함수 호출
// console.log(onAsyncTimer()); -> Promise를 암시적으로 리턴
// console.log('parent end'); -> 위 케이스와 마찬가지로 parent start, end를 먼저 보내고 자신의 할 일을 한다.
const onAsync2Timer = async () => {
console.log("parent start");
await onAsyncTimer();
console.log("parent end");
};
const onAsync3Timer = async () => {
console.log("parent parent start");
onAsync2Timer().then(() => {
console.log("parent parent end");
});
};
return (
<div>
<button className="bg-blue-100 p-10" onClick={onPromiseTimer}>
Promise Timer
</button>
<button className="bg-blue-300 p-10" onClick={onAsyncTimer}>
Async-await Timer
</button>
<button className="bg-blue-400 p-10" onClick={onAsync2Timer}>
Async-await2 Timer
</button>
<button className="bg-blue-500 p-10" onClick={onAsync3Timer}>
Async-await3 Timer
</button>
</div>
);
}
참고
https://ko.javascript.info/async-await
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function
'개발' 카테고리의 다른 글
[git] git repository 이동시 원격 저장소 연결 끊기 > 새로운 repository와 연결 (0) | 2022.08.18 |
---|---|
[JAVA] List 객체에 데이터 담을 때 같은 값이 들어가는 오류 (0) | 2022.05.12 |
[JAVA] #12. 배열[Array] 선언과 사용 방법 (0) | 2020.07.30 |