fetch APIで実験データを取得する
これまでJavaScriptで実験データの計算を行い、Node.jsでサーバー環境のファイル読み書きを学びました。しかしそのデータはすべてコード内に直接書き込むか、ローカルファイルから読み込んだものでした。実際の研究ではデータはネットワークの向こうのサーバーにあります — NCBIで遺伝子情報を検索し、社内のLIMSから試料リストを取得し、公開APIからタンパク質構造を取得します。
このプロセスがまさにAjaxです。外部シーケンシング業者にサンプルを送るのと同じです — 結果が届くまで実験室でぼーっと待たずに、他の実験を続けて、結果が届いたらその時確認します。Webも同じです。サーバーにデータをリクエストしておき、ブラウザは止まらずに動き続け、レスポンスが届いたら画面を更新します。
fetch:サーバーにデータをリクエストする
fetch()はサーバーに「このデータをください」とリクエストする関数です。NCBIに遺伝子名を入力して検索ボタンを押すのと同じです。
fetch("https://api.example.com/gene/TP53")
.then(function(response) {
return response.text();
})
.then(function(data) {
console.log(data);
});
console.log("リクエスト送信完了、他の作業を続行");このコードは3つのステップで動作します:
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の中で1回しか使いません。こういう場合、名前なしでそのまま入れることができます:
// 名前付き関数
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)形式です。実験室ですべての試料情報を決まった書式で記録するように、Webでは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)が作れます。1つの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の本質です。
1つ注意点 — 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コードは1行も変える必要がありません。サーバーの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)のように条件文では常に===を使ってください。