async 와 await 로 작성하는 읽기쉬운 비동기코드 # forEach
Javascript에서 비동기를 사용할 때 Promise라는 API를 정리해 보았었는데요.
오늘은 또다른 API인 aysnc와 await에 대해서 정리해 보도록 하겠습니다.
1. async와 await
Async와 await는 Javascript에서 비동기 처리를 쉽게 할 수 있게 해줍니다.
비동기 코드를 동기 코드처럼 읽고 쓰기 쉽게 만들어 주기 때문인데요.
콜백의 연쇄로 인한 복잡성을 피하고, 코드를 보다 직관적으로 만들 수 있게 도와줍니다.
Promise에 대한 기본적인 이해가 있다면, async와 await의 개념을 쉽게 받아들일 수 있습니다.
Promise에 대해서는 아래 글을 참조해 주세요.
>> Javascript의 Promise를 알아보자 #ES6
1-1. Code를 통한 Promise 와의 비교 및 이해
코드를 보면서 이해해 보도록 하겠습니다.
아래와 같이 Promise객체를 반환하는 myFunc라는 함수가 존재한다고 가정해 보겠습니다.
myFunc 함수는,
Promise의 Chaining을 붙여서 사용할수도 있구요.
async와 await를 이용할수도 있습니다.
A. Promise Chaining 방식
먼저 Promise 방식으로 보겠습니다.
"then()" Chaining을 통해서 코드를 작성합니다.
B. Async&Await 방식
async와 await 방식을 보겠습니다.
위에서 본 Promise 방식과 비교해 보면 어떤가요?
동기코드처럼 작성되었지만,
분명 Promise를 리턴해 주는 myFunc()함수를 실행해 주고요.
Promise가 resolve든, reject이건 리턴해 줄 때까지 기다려(await)줍니다.
1-2. async 와 await 정리
위에서 코드로 비교해 보았던, async와 await를 표로 정리해 보겠습니다.
구분 | 내용 |
async | 이 키워드를 function 앞에 붙이면 await를 사용할 수 있게 됩니다. 또한, resolved 또는 rejected 상태인 Promise객체를 return해 줍니다. ex> async function myFunc() {} |
await | async 키워드가 붙은 function 코드 내부에서, 비동기처리 함수앞에 붙여서 사용합니다. await가 붙은 비동기 함수의 Promise객체의 결과인 resolved 또는 rejected 가 return 될때까지, 처리를 대기하여 줍니다. 결과값이 나오면, 다시 처리를 재개합니다. ex> async function myFunc() { const result = await yourFunc(); //비동기함수인 yourFunc() console.log(result); } |
가장 중요한 것은,
비동기 함수앞에 await키워드를 붙여서,
Promise의 리턴결과가 resolve또는 reject가 나올 때가지 처리를 기다려 준다는 것 입니다.
1-3. Exception 처리(예외 처리)
A. try...catch
async와 await를 사용할 때 발생하는 예외는,
전통적인 try...catch 구문을 사용해서 처리할 수 있습니다.
이것은 동기 코드에서의 예외 처리와 매우 유사하게 작동합니다.
function myError() {
return new Promise((resolve, reject) => {
setTimeout(function() {
try {
throw new Error("에러발생");
resolve(value:"Good To Go");
} catch(err) {
reject(err);
}
}, timeout: 3 * 1000);
});
}
async function myErrorFunc() {
try {
const result = await myError();
return result;
} catch (e) {
console.log(e.message)
}
}
B. catch()
아래는 구글 공식문서에 나오는 코드인데요.
try...catch 블록 없이,
Promise 체인의 .catch() 메서드를 사용하여 에러를 처리할 수 있습니다.
async가 리턴하는 것은 Promise 객체이기 때문입니다.
이렇게 하면, async/await를 사용하는 비동기 함수에서 발생할 수 있는 예외를 처리할 때,
보다 함수형 프로그래밍에 가까운 접근 방식을 제공합니다.
async function test() {
const res = await blogger.blogs.get(params);
console.log(`The blog url is ${res.data.url}`);
}
test().catch(console.error);
1-4. Promise.all()에 사용하는 async & await
Promise.all()에도 아래와 같이 async와 await를 사용할 수 있습니다.
1-5. Babel과 이에 대한 사용
async나 await API는 모든 Browser에서 사용할수는 없는데요.
다만, async와 await를 이용한 코드를 작성한 후에, Babel 모듈을 이용해서 Compile한 후에 사용할 수는 있습니다.
Babel에 관해서는 아래 글을 참조해 주세요.
>> Babel 을 Webstorm 과 터미널 에 적용하는 방법 # Compiler ES6
2. forEach 와 비동기 처리
반복문에서 종종 사용되는 forEach를 사용하면 비동기처리에 적합하지 않은 경우가 발생합니다.
await를 사용할 수도 없는데요.
forEach가 비동기처리 작업을 완료될 때까지 기다려 주지 않기 때문입니다.
forEach 블록안에서는 아래와 같이 await를 쓸수가 없습니다.
그래서 다음의 2가지 방법을 사용해 주면 되는데요.
특히나 for..of 는 최신 API로서 forEach에서 break도 할 수 있도록 해 줍니다.
- for 문에 await를 걸어주기
- for..of 함수 이용하기(ES6의 최신 API)
아래와 같이 for문에 await를 걸어줄 수도 있습니다.
아래와 같이 for..of를 사용해 줄 수도 있습니다.
const arr = [1, 2, 3, 4, 5];
async function processData(item) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`${item}처리`);
resolve();
}, Math.random() * 1000);
});
}
async function processArray(arr) {
for (const item of arr) {
await processData(item);
}
console.log('모든 항목 처리 완료');
}
processArray(arr);
forEach안에서 꼭 활용해야 한다면, 익명함수의 인자앞에 async 키워드를 붙여주면 됩니다.
이렇게 하면 iteration이 끝나기를 기다리지 않고 비동기로 await함수가 처리가 됩니다.
webstorm 에서 async와 forEach를 사용한다면,
for..of를 쓰는 것을 권장하는데, 아래와 같이 노란색으로 표시가 됩니다.
3. 비동기함수를 동기적으로 처리하기
비동기 함수를 동기적으로 처리하는 일반적이고 효율적인 방법은,
비동기 작업을 Promise로 감싸고,
async 함수 내에서 await 키워드를 사용하여 프로미스의 해결을 기다리는 것입니다.
이 방법을 사용하면 비동기 코드를 동기적인 흐름처럼 작성하고 이해하기 쉬워지며, 비동기 처리의 복잡성을 감소시킬 수 있습니다.
function asyncFunc() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const randomNum = Math.random();
if (randomNum > 0.5) {
resolve('completed');
} else {
reject('failed');
}
}, 2000);
});
}
async function runAsyncFunc() {
try {
const result = await asyncFunc();
console.log(result);
} catch (error) {
console.error(error);
}
}
runAsyncFunc();
4. NodeJS의 Route함수에서 사용하기
Node.js의 라우트 함수에서 async/await를 사용하려면,
라우트 핸들러 함수를 async 함수로 선언해야 합니다.
이렇게 하면 함수 내에서 await를 사용하여 비동기 작업을 기다리도록 할 수 있습니다.
const express = require('express');
const router = express.Router();
const getUserData = async () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: "김군" });
}, 2000);
});
};
router.get('/api', async function (req, res) {
try {
const userData = await getUserData();
res.render('index', userData);
} catch (error) {
res.status(500).send("서버에서 오류가 발생했습니다.");
}
});
module.exports = router;