비동기적 JavaScript


JavaScript는 비동기적으로 동작한다. 다음 코드를 보자.

1
2
3
4
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log('done');

이 코드는 아마 아래와 같은 결과를 출력할 것이다.

1
2
3
4
5
6
7
8
9
10
11
0
1
2
3
4
5
6
7
8
9
done

뻔하다. 그럼 다음 코드를 보자.

1
2
3
4
5
6
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 10);
}
console.log('done');

일견 다를 바가 없어보이지만 출력한 결과는 사뭇 다르다.

1
2
3
4
5
6
7
8
9
10
11
done
10
10
10
10
10
10
10
10
10
10

엥? 왜 done이 먼저 출력되고 출력되지 말아야할 10만 열 번 출력되는 걸까?
그 이유는 JavaScript가 비동기적으로 동작하기 때문이다.

비동기적 JavaScript

비동기적이라 함은 전 명령의 수행이 끝나지 않아도 다음 명령을 실행한다는 의미이다.
일반적으로 학부생이 사용하게되는 C나 Java같은 절차적 언어의 경우 어떤 특정 명령을 실행하면 그 명령이 끝날 때 까지는 다음 명령을 실행하지 않는다. 별도의 스레드나 프로세스를 사용하지 않는 경우..

이와 다르게 JavaScript는 특정 명령이 실행된 후 그 명령이 끝나기 전에 다음 명령이 실행될 수 있다. 앞선 첫 코드에서는 명령이 동작하는데 걸리는 시간이 모두 동일하기에 예상한대로 순서대로 출력되었지만, 둘째 코드에서는 for문 내에서 출력하는 부분에 시간지연이 생겨 결국 done이 먼저 출력된 것이다. done이 출력되기전에 이미 forloop를 모두 반복하였으므로 i는 이미 10이 된 상태였고 그것을 열 번 출력했기 때문에 방금같은 결과가 나온 것이다.
JavaScript 코딩 시에는 서버쪽에 request를 날릴 일이 많은데 response를 받는 속도는 명령 실행속도보다는 훨씬 느리기 때문에 이걸 원래 짜왔던 대로 절차지향적으로 짜게 되면 문제가 생긴다.
따라서 JavaScript가 동기적으로 동작하길 원한다면 callback을 잘 이용해야 한다.

Callback

JavaScript에서는 함수도 객체이기 때문에 파라미터로 넘길 수 있고, 넘겨받은 함수를 언제 실행할지 결정할 수도 있다.
따라서, 모든 명령의 실행을 마친 후에 넘겨받은 함수객체를 실행시킬 수도 있는데 이것을 바로 Callback이라고 한다.
앞선 코드도 재귀함수와 콜백을 이용하면 setTimeout을 제거하지 않고도 예상한대로 동작하도록 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 먼저 함수를 정의한다.
repeatConsoleLog = function(i, callback) {
setTimeout(function() {
console.log(i);
if (i >= 9) {
callback();
} else {
repeatConsoleLog(i+1, callback);
}
}, 10);
}

// 이제 함수를 실행한다.
repeatConsoleLog(0, function() {
// 함수의 실행이 모두 끝난 뒤에 이곳에 있는 코드가 실행된다.
console.log('done');
});

이제 이 코드는 다음과 같은 결과를 출력한다.

1
2
3
4
5
6
7
8
9
10
11
0
1
2
3
4
5
6
7
8
9
done

이렇게 Callback을 함수가 끝나는 지점에서 실행시켜주면 함수가 종료되고 나서 Callback에 있는 명령어들이 실행된다.
Callback을 적절히 활용하면 이렇게 원하는 절차대로 코딩할 수 있다. 다만 일명 Callback 지옥에 빠질 수도 있다.

이렇게(…)

따라서 이런 문제를 해결해줄 Promise의 구현체를 사용하게 된다. 해당 내용에 대해서는 JavaScript Promise라는 포스팅에서 다루고 있으니 참고하면 좋겠다.