
JavaScript에서 trigger()를 사용해 이벤트를 강제로 호출하는 경우가 있다.
특히 select 태그가 여러 개 연쇄적으로 동작하는 화면에서 자주 사용된다.
하지만 아래와 같은 문제를 한 번쯤은 겪게 된다.
첫 번째
trigger는 정상 동작하는데
두 번째trigger는 실행되지 않는 것처럼 보인다.
이 글에서는 해당 현상이 발생하는 정확한 이유와 올바른 해결 방법을 정리한다.
selectA의 값이 변경되면
서버에서 데이터를 받아 selectB의 옵션을 세팅하고
selectB의 값을 기준으로 추가 로직을 수행하는 구조다.
$('#selectA').trigger('change');
$('#selectB').trigger('change');
$('#selectA').on('change', async function () {
const list = await fetch('/api/options').then(res => res.json());
setSelectOptions('#selectB', list);
});
$('#selectB').on('change', function () {
const value = $('#selectB').val();
console.log(value);
});
이 경우 다음과 같은 현상이 발생한다.
selectB의 change 이벤트가 실행되지 않음
또는 실행되지만 값이 undefined로 나옴
trigger()는 동기 실행이지만
select 옵션 세팅은 비동기다
#selectA.trigger('change')
서버 요청 시작 (fetch)
아직 옵션 세팅이 끝나지 않음
바로 #selectB.trigger('change') 실행
selectB에는 아직 옵션이 없음
즉, 옵션이 준비되기 전에 change 이벤트가 먼저 실행되는 구조다.
그래서 두 번째 trigger가 동작하지 않는 것처럼 보이게 된다.
$('#selectA').trigger('change');
setTimeout(() => {
$('#selectB').trigger('change');
}, 300);
이 방식은 다음과 같은 문제가 있다.
근본적인 해결책이 아니다.
옵션 세팅이 완료된 시점에서 다음 이벤트를 호출한다.
$('#selectA').on('change', async function () {
const list = await fetch('/api/options').then(res => res.json());
setSelectOptions('#selectB', list);
$('#selectB').trigger('change');
});
실행 흐름은 다음과 같다.
데이터 로드 완료
→ 옵션 세팅 완료
→ 다음 select 처리
옵션 세팅 자체를 명확한 비동기 작업으로 만든다.
function setSelectOptions(selector, list) {
return new Promise(resolve => {
const $select = $(selector);
$select.empty();
list.forEach(item => {
$select.append(
`<option value="${item.id}">${item.name}</option>`
);
});
resolve();
});
}
$('#selectA').on('change', async function () {
const list = await fetch('/api/options').then(res => res.json());
await setSelectOptions('#selectB', list);
$('#selectB').trigger('change');
});
이 방식의 장점은 다음과 같다.
이벤트를 로직 실행 수단으로 사용하지 않는다.
❌ 이벤트 체인 구조
A change → trigger B → trigger C
⭕ 데이터 흐름 기반 구조
async function loadSelectB() {
const list = await fetch('/api/options').then(res => res.json());
setSelectOptions('#selectB', list);
}
function handleSelectB() {
const value = $('#selectB').val();
console.log(value);
}
$('#selectA').on('change', async function () {
await loadSelectB();
handleSelectB();
});
이 구조의 특징은 다음과 같다.
trigger()는 이벤트를 흉내낼 뿐 흐름 제어 도구가 아니다
select 옵션 세팅은 대부분 비동기 작업이다
연속 trigger 호출은 타이밍 문제를 만든다
옵션 세팅이 완료된 이후에
다음 로직을 직접 호출해야 한다
select 연쇄 구조에서 발생하는 문제를 제대로 이해하면
불필요한 디버깅 시간을 크게 줄일 수 있다.