تاروپود — خودآموز HTML و CSS

فصل ۸ از ۹

پیشرفت ترم
۰٪

ترم ۷ · پل به JavaScript

گرفتن داده از اینترنت

فصل ۸پیش‌نمایش رایگان

در این فصل چه می‌سازی#

هر چیزی که تا امروز روی صفحه‌هایت بود، از داخل فایل‌های خودت می‌آمد؛ این فصل صفحه‌ات برای اولین بار دست دراز می‌کند و از اینترنت دادهٔ زنده می‌گیرد. یک کارتِ آب‌وهوا می‌سازی که با یک کلیک، دمای همین لحظه را از یک سرویسِ واقعی می‌گیرد و نشان می‌دهد. و نیمهٔ مهم‌ترِ فصل: کاری می‌کنیم که اگر اینترنت قطع بود، صفحه به‌جای شکستن یک پیامِ دوستانه نشان بدهد — صفحهٔ خوب صفحه‌ای است که روزِ بدِ شبکه هم سرِ پا بماند.

یک کارت وب که با بازویی کابلی بسته‌ای از داده را از یک ابر سرور می‌گیرد

آخر این فصل می‌توانی:

  • با fetch از یک API داده بگیری
  • توضیح بدهی promise چیست و async/await چه می‌کنند
  • جوابِ سرور را با res.ok بسنجی و JSON اش را بخوانی
  • با try/catch برای قطعیِ شبکه یک مسیرِ نجاتِ دوستانه بسازی

قبل از شروع#

  • از فصل ۴: addEventListener و رویداد click.
  • از فصل ۳: querySelector و textContent.
  • از فصل ۲: template literal.

مثل همیشه فقط یک مرورگر لازم داری، یا فقط همین آزمایشگاه.

۱. fetch: درخواستی که کدِ تو می‌فرستد#

تا حالا مرورگر فقط وقتی درخواست می‌فرستاد که تو آدرسی را باز می‌کردی. fetch همان کار را از داخلِ کد می‌کند: به یک آدرس درخواست بفرست و جواب را بگیر. آدرس‌هایی که به‌جای صفحهٔ HTML دادهٔ خام پس می‌دهند API نام دارند، و قالبِ رایجِ این داده JSON است — متنی ساخت‌یافته که تبدیل می‌شود به همان جنس چیزهایی که می‌شناسی: مقدارها، آرایه‌ها، و شیءهایی که با نقطه می‌خوانی‌شان، مثل event که از فصل ۴ با نقطه می‌خواندی.

یک تفاوتِ بزرگ با همهٔ کدهای قبلی‌ات: جوابِ اینترنت فوری نیست. برای همین fetch به‌جای جواب، بلافاصله یک promise می‌دهد — یک رسید که می‌گوید «جوابت در راه است». کلمهٔ await یعنی «همین‌جا بایست تا این رسید به جوابِ واقعی تبدیل شود»، و هر function ی که تویش await می‌نویسی باید جلویش async داشته باشد.

۲. اولین درخواست — و اولین undefined#

listener دکمه را بنویس؛ به async که قبل از function نشسته دقت کن:

const updateBtn = document.querySelector('#update-btn');
const weatherLine = document.querySelector('#weather-line');

const url = 'https://api.open-meteo.com/v1/forecast?latitude=35.69&longitude=51.39&current_weather=true';

updateBtn.addEventListener('click', async function () {
  const res = await fetch(url);
  const data = await res.json();
  console.log(data);
});

این آدرس سرویسِ آب‌وهوای Open-Meteo است — بدونِ ثبت‌نام و کلید — و آن دو عدد، عرض و طولِ جغرافیایی تهران‌اند. res جوابِ خام است؛ res.json() متنِ JSON را به دادهٔ قابلِ استفاده تبدیل می‌کند — و چون این کار هم زمان‌بر است، خودش هم promise می‌دهد؛ برای همین await دوم.

چک کن: دکمه را بزن و console را نگاه کن: یک شیءِ داده می‌بینی که تویش current_weather هست، با دما و سرعتِ باد.

حالا خرابیِ این فصل. await دوم را عمداً بردار — بنویس const data = res.json(); — و دکمه را بزن. در console به‌جای داده این را می‌بینی:

Promise {<pending>}

نه دما، نه خطا — فقط یک قولِ هنوز عملی‌نشده. و اگر از دلش چیزی بخوانی، undefined می‌گیری: داده‌ای که هنوز نرسیده.

🔧 اگر کار نکرد: هر جا انتظارِ داده داشتی و Promise یا undefined دیدی، اولین مظنون همیشه یکی است: یک await جا افتاده. await را برگردان و دوباره امتحان کن.

Checkpoint. قبل از ادامه، جواب بده: چرا جلوی functionِ این listener کلمهٔ async نشسته؟ (جواب: چون داخلش await نوشته‌ایم — قانون همین بود: هر functionی که تویش await باشد، جلویش async می‌خواهد. و await یعنی «بایست تا این promise به جوابِ واقعی برسد».)

۳. نمایش روی صفحه — و چکِ res.ok#

console.log فقط برای چشمِ توست؛ نمایش برای کاربر همان textContent همیشگی است. دادهٔ برگشتی را با نقطه بخوان و با template literal جمله بساز:

const data = await res.json();
const weather = data.current_weather;
weatherLine.textContent = `دمای الان: ${weather.temperature} درجه — باد: ${weather.windspeed} کیلومتر بر ساعت`;

چک کن: با یک کلیک، دمای واقعیِ همین لحظه روی کارت می‌نشیند.

ولی یک تلهٔ بی‌صدا اینجاست. آدرس را عمداً خراب کن — forecast را بکن forecas — و دکمه را بزن. fetch خطا نمی‌دهد! سرور جواب داده، فقط جوابش «چنین چیزی ندارم» است. آن‌وقت data.current_weather وجود ندارد و console این خطای واقعی را می‌دهد:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'temperature')

🔧 اگر کار نکرد: پیام Cannot read properties of undefined تقریباً همیشه یعنی «داده‌ای که فکر می‌کردی هست، نیست». قبل از خواندن، سالم‌بودنِ جواب را بپرس: res.ok فقط وقتی true است که سرور جوابِ سالم داده باشد.

if (res.ok) {
  const data = await res.json();
  const weather = data.current_weather;
  weatherLine.textContent = `دمای الان: ${weather.temperature} درجه — باد: ${weather.windspeed} کیلومتر بر ساعت`;
} else {
  weatherLine.textContent = 'سرور جواب داد، ولی نه جوابی که می‌خواستیم. بعداً دوباره امتحان کن.';
}

۴. وقتی اینترنت قطع است: try/catch#

جوابِ بد یک حالت بود؛ حالتِ بدتر این است که اصلاً جوابی نیاید — اینترنت قطع باشد یا سرور خاموش. آن‌وقت خودِ fetch شکست می‌خورد و کدت وسطِ راه می‌ترکد؛ در console چیزی شبیه TypeError: Failed to fetch می‌بینی. مهارش با یک جفت‌کلمهٔ ساده است: try یعنی «این‌ها را امتحان کن» و catch یعنی «اگر هر کدام شکست، به‌جای ترکیدن بیا اینجا». کدِ نهایی با هر دو مسیرِ نجات:

// script.js
const updateBtn = document.querySelector('#update-btn');
const weatherLine = document.querySelector('#weather-line');

const url = 'https://api.open-meteo.com/v1/forecast?latitude=35.69&longitude=51.39&current_weather=true';

updateBtn.addEventListener('click', async function () {
  try {
    const res = await fetch(url);

    if (res.ok) {
      const data = await res.json();
      const weather = data.current_weather;
      weatherLine.textContent = `دمای الان: ${weather.temperature} درجه — باد: ${weather.windspeed} کیلومتر بر ساعت`;
    } else {
      weatherLine.textContent = 'سرور جواب داد، ولی نه جوابی که می‌خواستیم. بعداً دوباره امتحان کن.';
    }
  } catch (error) {
    console.log(error);
    weatherLine.textContent = 'دسترسی به اینترنت نیست یا سرور در دسترس نیست — صفحه سرِ جایش است، بعداً دوباره امتحان کن.';
  }
});

حالا آزمایشِ نهایی: آدرس را با یک دامنهٔ ناموجود عوض کن — مثلاً https://no-such-server.example — و دکمه را بزن.

چک کن: به‌جای صفحهٔ شکسته یا سکوت، پیامِ دوستانهٔ خودت را می‌بینی — و بقیهٔ کارت، تیتر و متنش، سرِ جایش است. صفحه بدونِ داده هم هنوز یک صفحهٔ سالم و مفید است؛ به این می‌گویند progressive: داده تزئینِ صفحه است، نه ستونش.

دو مسیر از یک ابر سرور به یک کارت وب: مسیر سالم که بسته‌ای از داده را می‌رساند، و مسیر بریده‌شده که به یک پیام نرم و امن روی همان کارت ختم می‌شود

💡 نکته: آدرس‌ها عوض می‌شوند و سرویس‌ها روزی خاموش می‌شوند — قول نمی‌دهیم این API همیشه باشد. چیزی که می‌ماند الگوست: fetch ← چکِ res.ok ← خواندنِ JSON ← نمایش، و همیشه یک catch با پیامِ آبرومند. اگر این سرویس روزی نبود، صفحه‌ات نمی‌شکند؛ همان پیامِ catch را نشان می‌دهد. الگو را که داشته باشی، هر API دیگری همین است.

و برای خطاهایی که هنوز نمی‌شناسی، یک همراه داری:

🤖 با AI: خطای قرمزِ console را کپی کن و به دستیار AI ات بده: «این خطا یعنی چه؟ مشکل از آدرس است، از اینترنت، یا از کدِ من؟» بعد جوابش را در آزمایشگاه محک بزن — اگر گفت آدرس خراب است، آدرسِ سالم را برگردان و ببین واقعاً درست می‌شود. پیام‌های شبکه بهترین تمرینِ «AI هم اشتباه می‌کند» هستند، چون AI کد و اینترنتِ تو را ندیده و فقط حدس می‌زند.

کدِ کاملِ این فصل#

script.js را بالا دیدی؛ این هم index.html:

<!-- index.html -->
<!DOCTYPE html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>کارت آب‌وهوا</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main class="card">
      <h1>آب‌وهوای تهران</h1>
      <p>این کارت با یک کلیک، دادهٔ زندهٔ آب‌وهوا را از اینترنت می‌گیرد.</p>
      <p id="weather-line">هنوز داده‌ای نگرفته‌ایم — دکمه را بزن.</p>
      <button id="update-btn" type="button">به‌روزرسانی آب‌وهوا</button>
    </main>
    <script src="script.js"></script>
  </body>
</html>

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
fetch فِچ فرستادنِ درخواست به یک آدرس، از داخلِ کد
API اِی‌پی‌آی آدرسی که به‌جای صفحه، دادهٔ خام پس می‌دهد
JSON جِی‌سان قالبِ متنیِ داده که به مقدار و آرایه و شیء تبدیل می‌شود
promise پرامیس رسیدِ جوابی که هنوز نرسیده
async / await اِی‌سینک / اِوِیت علامتِ functionِ منتظر / «بایست تا جواب برسد»
res.ok رِس دات اُکِی آیا جوابِ سرور سالم است؟
try / catch ترای / کَچ امتحان کن؛ اگر شکست، مسیرِ نجات

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

جعبه‌ابزارت کامل شد: صفحه می‌سازی، استایل می‌دهی، رویداد می‌گیری، از داده فهرست می‌سازی و حالا از اینترنت داده می‌گیری. فصل بعد — آخرین فصلِ تاروپود — هیچ چیزِ تازه‌ای یاد نمی‌گیری؛ همه‌چیز را کنارِ هم می‌گذاری: یک صفحهٔ آشنا را برمی‌داری و با همین ابزارها جانش می‌دهی.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.