본문 바로가기

개발/JavaScript

async: true로 바꾸면 끝일까? 동기와 비동기 다시 정리하기

최근 지도 기능을 수정하면서 AJAX 호출 부분을 보다가 이런 코드를 다시 보게 됐다.

 
$.ajax({
    url: url,
    type: "POST",
    async: false,
    ...
});
 

async: false

예전부터 많이 봤던 옵션이긴 한데, 이번에는 이 부분을 비동기로 변경해야 하는 상황이 생겼다.

 
async: true
 

처음에는 간단하게 생각했다.

true가 비동기고 false가 동기니까
그냥 true로 바꾸면 되는 거 아닌가?

그런데 막상 수정하려고 보니 하나가 마음에 걸렸다.

그럼 기존에는 왜 굳이 false로 해놨을까?

그냥 오래된 코드라서 그런 건가?

아니면 여기서는 API 결과가 올 때까지 기다려야 해서 일부러 동기로 만들어놓은 건가?

괜히 true로 바꿨다가 다른 기능에서 문제가 생기는 건 아닐까 싶어서 코드를 조금 더 찾아봤다.

그러다 보니 이번 기회에 동기와 비동기가 실제 코드에서 어떻게 다르게 동작하는지 다시 정리하게 됐다.


일단 false면 기다린다

동기는 쉽게 생각하면 앞의 작업이 끝날 때까지 기다렸다가 다음 작업을 하는 방식이다.

예를 들어 이런 코드가 있다고 해보자.

 
console.log("1. 시작");

$.ajax({
    url: "/api/data",
    async: false,
    success: function(data) {
        console.log("2. API 완료");
    }
});

console.log("3. 다음 작업");
 

동기 방식에서는 AJAX 요청이 끝날 때까지 아래 코드가 기다린다.

그래서 실행 순서도 예상하기 쉽다.

1. 시작
2. API 완료
3. 다음 작업
 

API 요청을 보내고 응답을 받은 다음에야 아래 코드로 내려간다.

코드만 보면 편하다.

그냥 위에서부터 순서대로 보면 되니까.


true로 바꾸면 순서가 달라진다

이번에는 다른 건 건드리지 않고 이것만 바꿔보자.

 
async: true
 

비동기로 호출하면 API 응답을 기다리는 동안 아래 코드가 먼저 실행될 수 있다.

1. 시작
3. 다음 작업
2. API 완료
 

처음 비동기를 배울 때는 이게 조금 이상했다.

코드는 분명

1
2
3
 

순서로 적혀 있는데 왜 결과는 1 → 3 → 2로 나오지?

지금 생각하면 단순하다.

API 요청을 보내놓고

"응답 오면 알려줘. 나는 그동안 다른 거 하고 있을게."

하는 느낌이다.

그래서 API 응답을 기다리는 동안 아래 JavaScript가 계속 실행되고, 응답이 도착하면 그때 success가 실행된다.

 

그런데 동기가 더 편해 보이는데?

여기까지 보면 이런 생각도 든다.

그럼 그냥 동기로 쓰는 게 편한 거 아닌가?

실행 순서만 생각하면 확실히 편하다.

A가 끝난 다음 B가 실행된다는 게 보장되니까.

특히 기존 코드를 보다 보면 이런 형태가 있을 수 있다.

 
var data;

$.ajax({
    url: "/api/data",
    async: false,
    success: function(result) {
        data = result;
    }
});

drawLayer(data);
 

async: false라면 AJAX 요청이 끝난 뒤에 drawLayer(data)가 실행된다.

그러니까 drawLayer()를 호출하는 시점에는 data가 들어있다고 생각할 수 있다.

여기서 내가 처음 궁금했던 게 다시 나온다.

기존에는 왜 false로 해놨을까?

이런 실행 순서를 맞추기 위해서였을 수도 있다.

그래서 기존 코드에서 async: false를 발견했다고 무조건

오래된 방식이네. true로 바꾸자.

 

라고 생각하면 안 될 것 같았다.

일단 그 아래에서 응답 결과를 바로 사용하고 있는지부터 확인해야 한다.


그런데 왜 굳이 비동기로 바꾸려고 했을까?

동기가 순서도 확실하고 코드도 보기 편한데 그냥 쓰면 안 되나 싶었다.

문제는 기다리는 동안 화면까지 영향을 받을 수 있다는 것이다.

API 응답이 빠르면 크게 티가 안 날 수도 있다.

그런데 응답이 2초, 3초씩 걸리면 얘기가 달라진다.

특히 내가 보고 있던 건 지도 화면이었다.

지도는 사용자가 계속 움직인다.

드래그도 하고,

확대도 하고,

축소도 하고,

레이어도 켰다 껐다 한다.

그런데 중간에 동기 요청이 들어가서 응답을 기다리고 있으면 그동안 화면이 멈춘 것처럼 느껴질 수 있다.

서버 응답을 기다려야 하는 건 어쩔 수 없더라도 그동안 다른 동작까지 막을 필요는 없는 것이다.

이제 왜 비동기로 바꾸려고 하는지는 이해됐다.


그러다가 실제로 이런 오류도 봤다

이번 작업을 하면서 특이한 오류가 하나 있었다.

페이지를 새로고침하고 바로 지도를 움직이면 간헐적으로 오류가 발생했다.

Uncaught ReferenceError: sendJsonRequest is not defined
    at getBuoyList (...)
    at fn_toggleLayer (...)
 

그런데 항상 발생하는 건 아니었다.

새로고침한 다음 조금 기다렸다가 지도를 움직이면 정상적으로 동작했다.

이게 제일 이상했다.

기다렸다가 하면 되는데
새로고침하자마자 움직이면 왜 안 되지...?

 

처음에는 AJAX의 동기/비동기 문제랑 같이 보고 있었다.

둘 다 뭔가 타이밍 문제처럼 보였기 때문이다.


그런데 이 오류는 조금 다른 문제였다

처음에는

새로고침 직후에만 오류가 나네?
이것도 비동기 때문인가?

 

라고 생각하기 쉬웠다.

그런데 다시 보니 구분해서 볼 필요가 있었다.

Uncaught ReferenceError:
sendJsonRequest is not defined
 

이 오류는 말 그대로 sendJsonRequest()를 호출했는데, 호출한 시점에 브라우저가 해당 함수를 찾지 못했다는 뜻이다.

즉 AJAX 요청을 동기로 보내느냐 비동기로 보내느냐와는 별개로, JavaScript 파일의 로딩 순서나 함수가 실행되는 시점을 확인해봐야 하는 문제였다.

둘 다 실행 '순서'와 관련된 문제라 처음에는 비슷하게 느껴졌다.

하지만 같은 문제는 아니었다.

async: false를

 
async: true
 

로 바꾼다고 해서 sendJsonRequest is not defined가 자동으로 해결되는 건 아니다.

이건 따로 스크립트가 어떤 순서로 로드되는지, 해당 함수가 언제 정의되는지를 봐야 한다.

실제 오류를 보면서 이 부분을 구분하게 됐다.


false를 true로만 바꾸면 문제가 생길 수도 있다

다시 원래 코드로 돌아와서,

기존 코드가 이렇게 되어 있다고 해보자.

 
var data;

$.ajax({
    url: "/api/data",
    async: false,
    success: function(result) {
        data = result;
    }
});

drawLayer(data);
 

여기서 아무 생각 없이 async: true로 바꾸면:

 
var data;

$.ajax({
    url: "/api/data",
    async: true,
    success: function(result) {
        data = result;
    }
});

drawLayer(data);
 

drawLayer(data)가 먼저 실행될 수 있다.

API 응답이 아직 오지 않았다면 data에는 값이 없다.

처음에는 정말

false → true
 

한 줄만 바꾸면 되는 줄 알았다.

그런데 실제로는 그 요청의 결과를 기다리고 있는 코드까지 같이 봐야 했다.

비동기로 변경한다면 이런 식으로 응답 이후에 실행되도록 만들어야 한다.

 
$.ajax({
    url: "/api/data",
    async: true,
    success: function(data) {
        drawLayer(data);
    }
});
 

이제 drawLayer()는 API 응답이 온 다음 실행된다.

이 부분을 보고 나니까 왜 기존 코드가 동기로 작성되어 있었는지도 어느 정도 이해가 됐다.

당시에는 아마 "이 요청이 끝나야 다음 작업을 할 수 있다"는 흐름을 가장 간단하게 구현하려고 했을 수도 있다.

물론 지금도 그 방식으로 유지해야 한다는 뜻은 아니다.


그러다가 async/await도 다시 보게 됐다

여기까지 보다 보니 또 하나 궁금해졌다.

요즘 JavaScript에서는 이런 코드를 많이 쓴다.

 
async function loadData() {

    const response = await fetch("/api/data");
    const data = await response.json();

    drawLayer(data);
}
 

await.

이름부터 기다린다.

그러면 이것도 동기 아닌가?

처음에는 나도 이게 살짝 헷갈렸다.

그런데 async: false와는 차이가 있었다.

await을 만나면 해당 async 함수 안에서는 결과가 올 때까지 다음 줄의 실행을 미룬다.

그렇다고 그동안 브라우저 전체가 같이 멈춰버리는 건 아니다.

그래서 코드는 위에서 아래로 읽기 편하면서도 비동기로 처리할 수 있다.

 
const response = await fetch(...);
const data = await response.json();
drawLayer(data);
 

코드만 보면 순서대로 실행되는 것처럼 읽혀서 확실히 편하다.

예전 AJAX 코드를 보다 보니 오히려 async/await을 왜 많이 사용하는지도 조금 이해됐다.


처음에는 true, false만 보려고 했는데

처음 궁금했던 건 정말 단순했다.

async: true는 비동기.

async: false는 동기.

여기까지만 확인하고 넘어갈 수도 있었다.

그런데 실제 코드를 수정하려고 보니까 그것만 알고 바꾸면 안 됐다.

왜 기존 코드는 동기로 되어 있었는지,

그 요청이 끝나기를 기다리는 코드가 있는지,

비동기로 바꾸면 실행 순서가 어떻게 달라지는지,

그리고 지금 보고 있는 오류가 정말 AJAX 때문인지까지 같이 봐야 했다.

특히 기존 코드에서는 왜 이렇게 작성했는지를 먼저 보는 게 중요하다는 생각이 들었다.

async: false가 지금 기준으로 좋은 방식이 아니라고 해도, 당시 개발자가 그렇게 작성한 이유까지 없어지는 건 아니니까.

이번에 수정하면서 제일 기억에 남은 건 이거였다.

false를 true로 바꾸는 건 한 줄인데, 그 한 줄 때문에 실행 순서는 완전히 달라질 수 있다.

다음에 기존 코드에서 async: false를 발견하면 바로 바꾸기보다는 아래 코드부터 조금 더 내려가 볼 것 같다.

얘가 왜 기다리고 있었는지부터 봐야 하니까.