fetch API로 실험 데이터 가져오기
지금까지 JavaScript로 실험 데이터를 계산하고, Node.js로 서버 환경에서 파일을 읽고 쓰는 법을 배웠습니다. 하지만 그 데이터는 전부 코드 안에 직접 적어 넣거나, 로컬 파일에서 읽어온 것이었죠. 실제 연구에서는 데이터가 네트워크 너머 서버에 있습니다 — NCBI에서 유전자 정보를 검색하고, 사내 LIMS에서 시료 목록을 불러오고, 공개 API에서 단백질 구조를 받아옵니다.
이 과정이 바로 Ajax입니다. 외부 시퀀싱 업체에 시료를 보내는 것과 같습니다 — 결과가 올 때까지 실험실에서 멍하니 기다리지 않고, 다른 실험을 계속 진행하다가, 결과가 도착하면 그때 확인합니다. 웹에서도 똑같습니다. 서버에 데이터를 요청해놓고, 브라우저는 멈추지 않고 계속 작동하다가, 응답이 오면 화면을 갱신합니다.
fetch: 서버에 데이터 요청하기
fetch()는 서버에 "이 데이터 좀 줘"라고 요청하는 함수입니다. NCBI에 유전자 이름을 입력해서 검색 버튼을 누르는 것과 같습니다.
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
return response.text();
})
.then(function(data) {
console.log(data);
});
console.log("요청 보냄, 다른 작업 계속 진행");이 코드는 세 단계로 동작합니다:
fetch(url)— 서버에 요청을 보냅니다.then(function(response) { ... })— 응답이 도착하면 실행할 작업을 예약합니다.then(function(data) { ... })— 데이터를 받아서 처리합니다
핵심은 예약입니다. fetch는 응답을 기다리지 않고 바로 다음 줄로 넘어갑니다. 그래서 "요청 보냄, 다른 작업 계속 진행"이 먼저 출력되고, 서버 응답이 도착한 뒤에야 .then 안의 코드가 실행됩니다.
비동기: Ajax의 A
Ajax에서 가장 중요한 글자는 맨 앞의 A입니다 — Asynchronous(비동기).
유전자 분석 의뢰를 비유로 들어보겠습니다:
// 동기(Synchronous) 방식이라면:
// 1. 시퀀싱 업체에 시료 보냄
// 2. 결과 올 때까지 5일간 실험실에서 아무것도 안 하고 대기
// 3. 결과 도착, 그제야 다음 실험 시작
// → 실험실 가동률 최악
// 비동기(Asynchronous) 방식 — 실제로 이렇게 합니다:
// 1. 시퀀싱 업체에 시료 보냄
// 2. 기다리는 동안 세포 배양, 버퍼 준비, 논문 읽기
// 3. 결과 도착 알림 → 그때 확인하고 다음 단계 진행
// → 실험실 가동률 최적코드로 확인해보겠습니다:
function showGeneInfo() {
console.log("유전자 정보 수신 완료");
}
fetch("https://api.example.com/gene/BRCA1").then(showGeneInfo);
console.log(1);
console.log(2);실행 결과:
1
2
유전자 정보 수신 완료1, 2가 먼저 출력됩니다. fetch는 서버에 요청만 보내고 즉시 다음 줄로 넘어가기 때문입니다. 나중에 서버 응답이 도착하면 그때서야 showGeneInfo가 실행됩니다. 브라우저가 데이터를 다운로드하는 동안에도 스크롤, 클릭, 입력이 전부 정상 작동합니다. 이것이 비동기의 힘입니다.
익명함수로 코드 줄이기
위에서 showGeneInfo라는 이름을 붙인 함수는 .then 안에서 딱 한 번만 쓰입니다. 이런 경우 이름 없이 바로 집어넣을 수 있습니다:
// 이름 있는 함수
function showResult() {
console.log("결과 도착");
}
fetch("https://api.example.com/gene/TP53").then(showResult);
// 익명함수로 줄이기 — 동일한 동작
fetch("https://api.example.com/gene/TP53").then(function() {
console.log("결과 도착");
});.then 안의 함수에는 서버가 보내준 결과물이 자동으로 전달됩니다. 이 결과물을 받을 변수 이름은 자유롭게 지으면 됩니다 — 관례상 response라고 씁니다:
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
console.log(response);
});실험 보고서가 봉투에 담겨 오는 것을 떠올리세요. response는 그 봉투입니다 — 안에 실제 데이터뿐 아니라 "배송 성공 여부" 같은 메타정보도 들어 있습니다.
Response 객체: 봉투 뜯어보기
response에는 통신 결과에 대한 상세 정보가 담겨 있습니다:
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
console.log(response.status); // 200 (성공)
console.log(response.ok); // true
});주요 속성:
status— HTTP 상태 코드.200이면 성공,404면 데이터 없음ok— 상태 코드가 200~299 범위이면true
QC 검사처럼 응답 상태를 먼저 확인한 뒤 처리할 수 있습니다:
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
if (response.status === 404) {
console.log("해당 유전자를 찾을 수 없습니다");
} else if (response.status === 200) {
console.log("데이터 수신 성공");
}
});
console.assert(200 >= 200 && 200 <= 299, "200은 성공 범위");
console.assert(404 < 200 || 404 > 299, "404는 성공 범위 밖");JSON: 실험 데이터의 표준 양식
서버에서 받아온 데이터는 보통 JSON(JavaScript Object Notation) 형식입니다. 실험실에서 모든 시료 정보를 정해진 양식에 맞춰 기록하는 것처럼, 웹에서는 JSON이 그 표준 양식입니다.
// JSON 형식의 유전자 데이터
const geneDataJson = '{"name": "TP53", "chromosome": "17p13.1", "function": "tumor suppressor", "length_bp": 19149}';
// JSON 문자열 → JavaScript 객체로 변환
const gene = JSON.parse(geneDataJson);
console.log(gene.name); // "TP53"
console.log(gene.chromosome); // "17p13.1"
console.log(gene.length_bp); // 19149
console.assert(gene.name === "TP53", "유전자 이름 확인");
console.assert(typeof gene.length_bp === "number", "길이는 숫자 타입");fetch로 JSON 데이터를 받을 때는 response.json()을 씁니다:
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
return response.json();
})
.then(function(gene) {
console.log(gene.name); // "TP53"
console.log(gene.chromosome); // "17p13.1"
});response.text()는 응답을 순수 문자열로, response.json()은 JSON을 파싱해서 JavaScript 객체로 돌려줍니다. 실험 데이터처럼 구조화된 정보를 받을 때는 거의 항상 .json()을 씁니다.
여러 시료 데이터를 배열로 받는 경우도 흔합니다:
const samplesJson = '[{"id": "S001", "od": 0.85, "pass": true}, {"id": "S002", "od": 0.12, "pass": false}, {"id": "S003", "od": 1.23, "pass": true}]';
const samples = JSON.parse(samplesJson);
for (let i = 0; i < samples.length; i++) {
const status = samples[i].pass ? "PASS" : "FAIL";
console.log(`${samples[i].id}: OD=${samples[i].od} → ${status}`);
}
// S001: OD=0.85 → PASS
// S002: OD=0.12 → FAIL
// S003: OD=1.23 → PASS
console.assert(samples.length === 3, "시료 3개");
console.assert(samples[1].pass === false, "S002는 FAIL");SPA: 페이지 전체를 다시 불러오지 않는 앱
우리가 배운 fetch를 활용하면 SPA(Single Page Application)를 만들 수 있습니다. 하나의 HTML 페이지 안에서 필요한 부분만 갈아끼우는 방식입니다.
LIMS(Laboratory Information Management System)를 떠올려 보세요. 왼쪽 메뉴에서 "DNA 추출"을 클릭하면 오른쪽 영역에 해당 프로토콜이 나타나고, "PCR"을 클릭하면 같은 자리에 PCR 프로토콜이 나타납니다. 페이지 전체가 새로고침되지 않고, 내용 영역만 바뀝니다.
function loadProtocol(name) {
fetch("https://api.example.com/protocols/" + name)
.then(function(response) {
return response.text();
})
.then(function(content) {
document.querySelector("#protocol-content").innerHTML = content;
});
}
// 버튼 클릭 시 해당 프로토콜 불러오기
// loadProtocol("dna-extraction");
// loadProtocol("pcr");document.querySelector("#protocol-content")로 특정 영역을 콕 집어서, innerHTML로 그 안의 내용만 교체합니다. 나머지(헤더, 메뉴, 푸터)는 그대로 유지됩니다. 이것이 SPA의 핵심입니다.
한 가지 주의할 점 — innerHTML을 쓸 때 변경할 영역만 정확히 선택해야 합니다. 부모 요소를 선택하면 그 안의 다른 자식 요소까지 모두 날아갑니다. 겔 이미지에서 특정 레인만 잘라 붙이려다 옆 레인까지 지워버리는 실수와 같습니다.
데이터와 로직의 분리
좋은 코드는 데이터와 로직을 분리합니다. 실험 프로토콜(절차)과 시료 목록(데이터)을 같은 문서에 섞어 놓으면, 시료가 바뀔 때마다 프로토콜 문서를 건드려야 합니다. 실수로 절차를 망가뜨릴 위험이 생기죠.
코드도 마찬가지입니다. 유전자 목록 같은 데이터를 별도 파일(또는 서버)에 두고, 코드는 그 데이터를 fetch로 가져와서 처리하는 구조가 이상적입니다:
// 데이터: 서버의 JSON 파일에 저장
// [{"name": "TP53", "type": "tumor suppressor"},
// {"name": "BRCA1", "type": "DNA repair"},
// {"name": "EGFR", "type": "receptor tyrosine kinase"}]
// 로직: JavaScript로 데이터를 가져와서 화면에 표시
fetch("https://api.example.com/genes")
.then(function(response) {
return response.json();
})
.then(function(genes) {
let listHtml = "";
for (let i = 0; i < genes.length; i++) {
listHtml += "<li>" + genes[i].name + " — " + genes[i].type + "</li>";
}
document.querySelector("#gene-list").innerHTML = listHtml;
});유전자가 100개로 늘어나도 JavaScript 코드는 한 줄도 바꿀 필요가 없습니다. 서버의 JSON 데이터만 업데이트하면 됩니다.
직접 해보기 (Faded Example)
아래 빈칸을 채워 서버에서 유전자 정보를 가져와 화면에 표시하는 코드를 완성하세요.
fetch("https://api.example.com/gene/TP53").then(function() {return response.json();}).then(function(gene) {const info = gene.name + " (" + gene. + ")";document.querySelector("#result").innerHTML = ;});
흔한 에러 & 해결법
Q: .then 안에서 만든 변수를 바깥에서 쓰려면?
response나 gene은 .then 함수 안에서만 존재합니다. 바깥에서 접근하면 undefined가 됩니다. 데이터를 사용하는 모든 코드는 .then 안에 넣어야 합니다. 비동기의 결과는 "도착했을 때" 처리해야 하기 때문입니다.
Q: response.json()을 했는데 에러가 납니다
서버 응답이 유효한 JSON 형식이 아닐 때 발생합니다. 먼저 response.text()로 받아서 console.log로 실제 내용을 확인하세요. HTML 에러 페이지나 빈 응답이 올 수도 있습니다.
Q: fetch 요청이 작동하지 않습니다 (CORS 에러)
브라우저 콘솔에 "CORS" 관련 에러가 보인다면, 서버가 외부 접근을 허용하지 않는 것입니다. 로컬 개발 서버(npx serve)를 띄워서 같은 서버의 파일을 fetch하거나, CORS를 허용하는 공개 API를 사용하세요.
Q: =와 ===, ==의 차이가 헷갈립니다
=는 대입(값을 넣음), ===는 엄격 비교(타입과 값 모두 같은지), ==는 느슨한 비교(타입 변환 후 비교)입니다. if (response.status === 200)처럼 조건문에서는 항상 ===를 쓰세요.