본문 바로가기

Language/JavaScript(ES6)

비동기(Promise, Axios, Redux-thunk,async/await)

비동기

전체 로직 중 A로직의 처리를 기다리지 않고 처리 하다가 A로직이 처리를 완료 하면 A로직을 완료하는 기법이다.

 

웹에서 사용시에는 보통 데이터 처리는 화면의 렌더링보다 느리니, 화면의 렌더링을 일단 처리하고 데이터 처리는 요청이 완료 되었을 때, 화면에 뿌려주는 형식으로 사용된다.

 

그런데 단지 비동기를 그냥 처리만 할 경우에는 다음과 같은 문제점이 생긴다.

1
2
3
4
5
6
7
function a(){
    let greet;
    setTimeout(function(){greet="hello"},1000);
    console.log(greet); 
}
a() //undefined
 
 
a() 의 콘솔 결과는 undefined이다.
setTimeout은 비동기적으로 작동하는데

greet를 선언하고 setTimout을 기다리지 않고 console.log(greet) 부터 실행했기 때문이다.

 

결국 순서대로 실행되지 않는 것이 순서대로 실행하는 것이 필요하게 되버렸다.

 

이러한 문제점을 해결 하려고 callback이라는 기법을 이용했다.

1
2
3
4
5
6
7
8
9
10
11
function a(func){
    let greet;
    setTimeout(function(){
                greet="hello";
                func(greet)}
                ,1000);
}
 
a(function(word){
    console.log(word); // 1초 후에 hello가 출력.
});

요점은 전체적인 코드에서는 비동기적이지만, 비동기 함수 내에선 동기적이라는 점을 이용한 것이다.

비동기 함수 안에 function을 주입하여 실행하는 식이다.

 

하지만 이렇게 작업하는것은 코드를 복잡하게 만들고, 콜백헬이라는 문제를 일으키거나, 우리의 정서상에도 좋지 않다.

 

그것을 위해서 자바스크립트는 Promise라는 객체를 만들었다.

 

Callback Hell과 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Timer(number, fn) {
    setTimeout(
        function() { console.log(number); fn(); },
        1000
    );
}
 
Timer(1function() {
    Timer(2function() {
        Timer(3function() {
            Timer(4);
        })
    })
})
 

< callback으로 만든 타이머 >

1초마다 1씩 늘어나는 값을 찍을 것이다.

문제는... 이해하기도 어렵고, 작성도 어렵다.

이런 것들이 계속 늘어나게 되면서 callback Hell에 빠지게 된다.

 

이를 Promise 객체를 이용해서 해결 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Timer(number) {
    return new Promise( 
        (resolve, reject) => {
            setTimeout
                () => {
                    console.log(number);
                    resolve(); 
                },
                1000
            )
        }
    )
}
 
 
Timer(1)
.then(() => Timer(2))
.then(() => Timer(3))
.then(() => Timer(4))
.then(() => Timer(5))
.then(() => Timer(6))

Promise 객체는 resolve와 reject를 파라미터로 가지는 함수를 생성자로 가지는데,

resolve는 처리가 성공했을 때, callback으로 부를 함수가 되어 실행된다.

반대로 reject는 처리가 실패했을 때 에러를 일으키기 위해 사용하는 함수이다.

 

사용은 다음과 같이 .then(callbackFunction)으로 사용가능하다.

reject는 catch를 통해 받을 수 있다.

 

1
2
3
4
5
6
7
8
9
function Error() {
  return new Promise(function (resolve, reject) {
    reject(new Error("Error"));
  });
}
 
Error().then().catch(function (err) {
  console.log(err); // Error: Error
});
 

< reject 사용 예제 >

이와 같이 사용하므로써 의미가 훨씬 직관적이여졌다.

 

Axios

axios는 Promise 객체를 리턴하게 생성된 객체이다. 그래서 다음과 같이 사용되어 왔다.

Redux thunk

redux thunk는 액션생성자가 객체를 반환하는 것이 아니라 함수(dispatcher)를 사이에 넣어 dispatch가 객체를 반환하게 한다.

이는 비동기의 callback이 작동하는 것과 아주 유사하다. 이를 이용하면 action과 state를 보고 비즈니스로직을 실행 후 리듀서로 전달할 수 있게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
 
function increment() {
    return {
        type: INCREMENT_COUNTER
    };
}
 
function incrementAsync() {
    return dispatch => {     
        setTimeout(() => {
            dispatch(increment());
        }, 1000)
    }
}
 

redux-thunk의 document의 예제이다.

보시다시피

increment라는 액션생성자를 만들고

incrementAsync라는 새로운 함수를 만든 후 액션 생성자를 비동기적으로 실행시킨다.

1
2
3
4
5
6
7
8
9
10
11
function incrementIfOdd() {
    return (dispatch, getState) => {
        const { counter } = getState();
 
        if(counter % 2 === 0) {        
            return;
        }
        
        dispatch(increment());
    }
}
 

위의 코드는 state를 가져와서 조건이 맞을 시에만 액션 생성자가 실행된다.

1
2
3
4
5
6
7
8
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
 
const store = createStore(
    rootReducer,
    applyMiddleware(thunk)
)
 

물론 리덕스가 함수를 받을 리는 없으므로 thunk를 미들웨어에 추가 해주어야 한다.

 

Async/Await

Promise의 호출마저 불편을 겪었나 보다. Async와 Await라는 것이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Timer(number) {
    return new Promise( 
        (resolve, reject) => {
            setTimeout
                () => {
                    console.log(number);
                    resolve(); 
                },
                1000
            )
        }
    )
}
 
 
Timer(1)
.then(() => Timer(2))
.then(() => Timer(3))

아까의 예제이다. 이를 Async를 이용하면 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Timer(number) {
    return new Promise( 
        (resolve, reject) => {
            setTimeout
                () => {
                    console.log(number);
                    resolve(); 
                },
                1000
            )
        }
    )
}
 
 
async function test(){
    await Timer(1);
    await Timer(2);
    await Timer(3);
}
 

훨씬 직관적이다.

함수를 리턴하고 자시고 없이 바로 함수를 실행시켜주기만 하면 될 뿐만 아니라.

함수 내에서 중간에 다른 비즈니스 로직들도 사용 가능하다.

 

다만 주의할 점은 await는 무조건 Promise객체를 받아야 한다는 점이다.