Fetching Experiment Data with the fetch API
So far you've calculated experiment data with JavaScript and read/written files in a server environment with Node.js. But all that data was either hardcoded in your code or read from local files. In real research, data lives on servers across the network โ you search gene info on NCBI, pull sample lists from your lab's LIMS, and retrieve protein structures from public APIs.
This process is called Ajax. Think of it like sending samples to an external sequencing service โ you don't just sit in the lab waiting until results arrive. You continue other experiments and check the results when they come back. It works the same way on the web. You send a data request to the server, the browser keeps running without freezing, and updates the screen when the response arrives.
fetch: Requesting Data from a Server
fetch() is a function that asks the server "please give me this data." Like typing a gene name into NCBI and hitting the search button.
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
return response.text();
})
.then(function(data) {
console.log(data);
});
console.log("Request sent, continuing other work");This code operates in three steps:
fetch(url)โ sends a request to the server.then(function(response) { ... })โ schedules work to run when the response arrives.then(function(data) { ... })โ receives and processes the data
The key word is schedules. fetch doesn't wait for the response โ it immediately moves to the next line. That's why "Request sent, continuing other work" prints first, and the code inside .then only runs after the server response arrives.
Asynchronous: The A in Ajax
The most important letter in Ajax is the first one โ A for Asynchronous.
Let's use a gene analysis request as an analogy:
// If it were synchronous:
// 1. Send samples to sequencing company
// 2. Wait 5 days in the lab doing nothing
// 3. Results arrive, only then start next experiment
// โ Lab utilization: terrible
// Asynchronous โ how it's actually done:
// 1. Send samples to sequencing company
// 2. While waiting: culture cells, prepare buffers, read papers
// 3. Results notification arrives โ check then and proceed
// โ Lab utilization: optimalLet's verify with code:
function showGeneInfo() {
console.log("Gene info received");
}
fetch("https://api.example.com/gene/BRCA1").then(showGeneInfo);
console.log(1);
console.log(2);Output:
1
2
Gene info received1, 2 print first. That's because fetch only sends the request and immediately moves to the next line. showGeneInfo only runs later when the server response arrives. While the browser downloads data, scrolling, clicking, and typing all work normally. That's the power of asynchronous.
Shortening Code with Anonymous Functions
The named function showGeneInfo above is only used once inside .then. In cases like this, you can insert it directly without a name:
// Named function
function showResult() {
console.log("Result arrived");
}
fetch("https://api.example.com/gene/TP53").then(showResult);
// Anonymous function โ same behavior
fetch("https://api.example.com/gene/TP53").then(function() {
console.log("Result arrived");
});The server's response is automatically passed to the function inside .then. You can name the parameter whatever you want โ by convention, it's called response:
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
console.log(response);
});Think of a lab report arriving in an envelope. response is that envelope โ it contains not just the actual data but meta-information like "was the delivery successful?"
The Response Object: Opening the Envelope
The response contains detailed information about the communication result:
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
console.log(response.status); // 200 (success)
console.log(response.ok); // true
});Key properties:
statusโ HTTP status code.200means success,404means not foundokโtrueif the status code is in the 200โ299 range
Like a QC check, you can verify the response status before processing:
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
if (response.status === 404) {
console.log("Gene not found");
} else if (response.status === 200) {
console.log("Data received successfully");
}
});
console.assert(200 >= 200 && 200 <= 299, "200 is in success range");
console.assert(404 < 200 || 404 > 299, "404 is outside success range");JSON: The Standard Format for Experiment Data
Data from servers is usually in JSON (JavaScript Object Notation) format. Just as labs record all sample info in a standardized form, JSON is that standard form on the web.
// Gene data in JSON format
const geneDataJson = '{"name": "TP53", "chromosome": "17p13.1", "function": "tumor suppressor", "length_bp": 19149}';
// Convert JSON string โ JavaScript object
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", "Gene name verified");
console.assert(typeof gene.length_bp === "number", "Length is number type");When receiving JSON data with fetch, use 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() returns the response as a plain string, while response.json() parses JSON and returns a JavaScript object. For structured data like experiment results, you'll almost always use .json().
Receiving multiple sample data as an array is also common:
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 samples");
console.assert(samples[1].pass === false, "S002 is FAIL");SPA: Apps That Don't Reload the Entire Page
Using the fetch we learned, you can build SPAs (Single Page Applications). These work by swapping only the needed parts within a single HTML page.
Think of a LIMS (Laboratory Information Management System). Click "DNA Extraction" in the left menu and the corresponding protocol appears in the right panel. Click "PCR" and the PCR protocol appears in the same spot. The entire page doesn't reload โ only the content area changes.
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;
});
}
// Load protocol on button click
// loadProtocol("dna-extraction");
// loadProtocol("pcr");document.querySelector("#protocol-content") pinpoints a specific area, and innerHTML replaces only its contents. Everything else (header, menu, footer) stays intact. This is the essence of SPA.
One important note โ when using innerHTML, you must select only the exact area you want to change. If you select a parent element, all its child elements will be wiped out too. It's like trying to cut and paste a specific lane from a gel image and accidentally erasing the adjacent lanes.
Separating Data and Logic
Good code separates data from logic. If you mix experiment protocols (procedures) and sample lists (data) in the same document, you have to touch the protocol document every time samples change. That risks accidentally breaking the procedure.
Code works the same way. Ideally, data like gene lists lives in a separate file (or server), and your code uses fetch to retrieve and process it:
// Data: stored in a JSON file on the server
// [{"name": "TP53", "type": "tumor suppressor"},
// {"name": "BRCA1", "type": "DNA repair"},
// {"name": "EGFR", "type": "receptor tyrosine kinase"}]
// Logic: JavaScript fetches data and displays on screen
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;
});Even if genes grow to 100, not a single line of JavaScript needs to change. Just update the JSON data on the server.
Try It Yourself (Faded Example)
Fill in the blanks to complete code that fetches gene info from a server and displays it on screen.
fetch("https://api.example.com/gene/TP53").then(function() {return response.json();}).then(function(gene) {const info = gene.name + " (" + gene. + ")";document.querySelector("#result").innerHTML = ;});
Common Errors & Solutions
Q: How do I use variables created inside .then outside it?
response or gene only exist inside the .then function. Accessing them outside gives undefined. All code that uses the data must go inside .then. Async results must be processed "when they arrive."
Q: response.json() throws an error
This happens when the server response isn't valid JSON. First use response.text() and console.log to check the actual content. It might be an HTML error page or an empty response.
Q: fetch request doesn't work (CORS error)
If you see a "CORS" error in the browser console, the server isn't allowing external access. Either run a local dev server (npx serve) to fetch files from the same server, or use a public API that allows CORS.
Q: What's the difference between =, ===, and ==?
= is assignment (sets a value), === is strict comparison (checks both type and value), == is loose comparison (converts types before comparing). In conditionals like if (response.status === 200), always use ===.