[javascript] 비동기처리
동기식과 비동기식
동기식(Synchronous) | 하나의 작업이 시작하면, 마칠 때 까지 기다렸다가 다음 작업을 수행하는 방식 작업의 순서가 정해져있다 |
비동기식(Asynchronous) | 하나의 작업이 완료되기를 기다리지 않고 다음 작업을 수행하는 방식 보통 위에서 아래로 코드를 읽어나가지만 늦게 시작한 작업이 먼저 마칠 수 있다 |
우리는 코드를 작성하고, 읽어나갈 때 작업이 당연히 동기식으로 이루어질 것이라고 생각한다. 하지만 프로그램은 이전 작업이 오래 걸린다고 기다려주지 않고, 다음 작업을 시작한다. 이런 방식때문에 시간이 오래 걸리는 작업을 프로그램 중간에 수행할 경우, 오류가 생기기 쉽다.
시간이 오래 걸리는 작업의 경우 주로 온라인상으로 통신해 작업을 수행하거나, 자료를 가져오는 경우이다. 가져온 자료를 가지고 다음 작업을 수행할 거라고 생각했는데 평소처럼 코드를 작성하면 원하지 않는 자료가 나올 수 있다.
입력한email과 비밀번호를 이용해서 firebase에서 인증을 시도한 후, user의 정보를 가져오려고 한다.
1
2
3
4
|
const signin = ({ email, password }) => {
const user = signInWithEmailAndPassword(auth, email, password);
console.log(user);
};
|
cs |
firebase에서 제공하는 signInWithEmailAndPassword를 사용해서 정보를 가져오고, 콘솔에 출력해도 결과가 나오지 않는다.
firebase와의 통신에 시간이 오래 걸리기 때문에 user 정보를 받아오기 전에 console에 user를 출력한다.
위와 같이 원하는 정보를 가져오기 위해서는 비동기 처리를 해줘야 한다.
비동기처리
원래 비동기식으로 처리되는 코드를 개발자가 원하는 순서대로 처리하도록 프로그래밍 하는 것을 비동기처리라고 한다.
(비동기식: 기다리지않고 다음 작업을 수행하는 방식 / 비동기처리: 비동기식 처리 내에서 작업의 순서를 동기식으로 처리하도록 만드는 것)
(비동기식프로그래밍 / 비동기처리 라고 말하기도 한다. 둘다 비동기~~이지만 의미는 반대이다)
비동기처리의 종류
1. 콜백함수(Callback)
2. Promise
3. async / await
1. 콜백(Callback)
콜백함수는 다른 함수의 인자로 넘기는 함수를 말한다.
1
2
3
4
5
6
7
8
|
const signin = ({ email, password }, callback) => {
const user = signInWithEmailAndPassword(auth, email, password);
callback(user);
};
signin({ "email@email.com", "abcdef" }, function(user) {
console.log(user)
})
|
cs |
signin 함수 안에 새로운 함수를 넘겨주면, user의 정보를 받아온 후 callback함수를 실행한다.
먼저 실행한 함수의 결과값을 반환하지 않고 결과값으로 처리할 로직을 콜백함수로 넘기면 원하는 방식으로 코드를 작성할 수 있다.
하지만 여러 단계를 거치는 작업의 경우, 콜백함수가 쉽게 복잡해 질 수 있다.
아래와 같은 상태를 콜백지옥이라고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// Callback hell
a(function (resultsFromA) {
b(resultsFromA, function (resultsFromB) {
c(resultsFromB, function (resultsFromC) {
d(resultsFromC, function (resultsFromD) {
e(resultsFromD, function (resultsFromE) {
f(resultsFromE, function (resultsFromF) {
console.log(resultFromF);
})
})
})
})
})
});
|
cs |
위와 같은 상태에서는 가독성이 떨어지고, 오류를 처리하기 어려워진다. javascript에서 Promise와 async/await을 사용할 수 있게 되면서 콜백함수 대신 Promise와 async/await를 사용하도록 권장하는 추세라고 한다.
2. Promise
Promise는 자바스크립트 비동기 처리에 사용되는 객체이다.
프로미스는 미래의 어떤 시점에 결과를 제공하겠다는 약속(Promise)를 반환한다.
프로미스는 세 가지 중 하나의 상태를 가진다
- 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
- 이행(fulfilled): 연산이 성공적으로 완료됨.
- 거부(rejected): 연산이 실패함.
Promise는 이행되거나 거부될 수 있다. 이행되면 .then()에 따라 다음 작업이 수행된다. 거부되거나 에러가 나타나면 .catch()에 따라 정해진 작업을 수행한다. 이런 특성 덕분에 비동기처리가 가능해진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let myFirstPromise = new Promise((resolve, reject) => {
// 우리가 수행한 비동기 작업이 성공한 경우 resolve(...)를 호출하고, 실패한 경우 reject(...)를 호출한다.
// 이 예제에서는 setTimeout()을 사용해 비동기 코드를 흉내낸다.
// 실제로는 여기서 보통 XHR이나 HTML5 API를 사용한다.
setTimeout( function() {
resolve("성공!") // 와! 문제 없음!
}, 250)
})
myFirstPromise.then((successMessage) => {
// successMessage는 위에서 resolve(...) 호출에 제공한 값이다.
console.log("와! " + successMessage) // '와! 성공!'이 출력된다.
});
|
cs |
myFirstPromise에서는 setTimeout을 사용한다. myFirstPromise가 Promise가 아니라면 어떻게될까?
위의 코드는 Promise를 사용했고, 아래에는 Promise를 사용하지 않은 코드이다.
Promise를 사용한 코드는 setTimeout을 통해 받아온 '성공!'을 가져와 사용할 수 있었지만 일반 함수로 작성한 경우에는 setTimeout을 실행하기 전에 다음 코드가 실행되어 undefined를 출력하는 것을 확인할 수 있다.
Promise에서 reject이 실행되면 .them()의 코드는 실행되지 않고, .catch()가 있다면 그 안의 코드를 실행한다.
.then() 은 Promise를 반환하기 때문에 .then()을 여러 개 중첩해서 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// callback hell 벗어나기
function a() {
return new Promise({
// ...
});
}
function b() {
return new Promise({
// ...
});
}
function c() {
return new Promise({
// ...
});
}
myFirstPromise()
.then(a)
.then(b)
.then(c);
|
cs |
3. async / await
async와 await 을 사용해 쉽게 비동기처리를 수행할 수 있다.
async function 함수명() {
await 비동기_처리_메서드_명();
}
함수 앞에 async를 붙여주고, 함수 내에서 Promise 기반 함수 앞에 await 키워드를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
|
function getUserName= () => {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
console.log("user : " + user.name);
})
.catch((error) => {
console.log("error ", error.message);
});
}
|
cs |
createUserWithEmailAndPassword 는 firebase에서 email, password를 사용해 인증한 후 userCredential을 반환하는 함수이다. 이 함수를 사용해 getUserName이라는 함수를 만들었다.
createUserWithEmailAndPassword가 잘 실행되면 콘솔창에 이름을 출력하고, 에러가 나타나면 error message를 띄운다.
이것을 async/await을 사용하면 아래와 같이 쓸 수 있다.
1
2
3
4
5
|
async function getUserName = () => {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
console.log("user : " + user.name);
}
|
cs |
전체 함수 앞에 async를 사용해야 해당 함수 내에서 await을 사용할 수 있다.
(*async 가 없는 함수 내에서 await을 사용한다면 에러가 나오니 꼭 같이 사용해줘야 한다.)
await은 뒤의 Promise기반 함수가 fullfill될 때 까지 기다렸다가 Promise객체를 받아서 다음 작업을 수행한다.
Promise를 사용할 때 에러를 처리하던 .catch()는 try/catch를 사용해 수행할 수 있다.
1
2
3
4
5
6
7
|
async function getUserName = () => {
try {
// const userCredential = await ... // ...
} catch (error) {
console.log("error " + error.message);
}
}
|
cs |
callback함수와 비교
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// callback hell 벗어나기
async function result = () => {
const resultsFromA = await a();
const resultFromB = await b(resultFromA);
const resultFromC = await c(resultFromB);
const resultFromD = await d(resultFromC);
const resultFromE = await e(resultFromD);
const resultFromF = await f(resultFromF);
console.log(resultFromF);
}
// callback hell 과 비교해보면 훨씬 가독성이 좋아진 것을 확인할 수 있다.
// a(function (resultsFromA) {
// b(resultsFromA, function (resultsFromB) {
// c(resultsFromB, function (resultsFromC) {
// d(resultsFromC, function (resultsFromD) {
// e(resultsFromD, function (resultsFromE) {
// f(resultsFromE, function (resultsFromF) {
// console.log(resultFromF);
// })
// })
// })
// })
// })
// });
|
cs |
참고한 사이트
https://helloworldjavascript.net/pages/285-async.html
https://www.daleseo.com/js-async-callback/
https://www.daleseo.com/js-async-promise/
https://velog.io/@change/JavaScript-asyncawait%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C
https://joshua1988.github.io/web-development/javascript/javascript-asynchronous-operation/
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/ko/docs/Learn/JavaScript/Asynchronous/Async_await