کار با APIها در جاوا اسکریپت: Fetch API و AJAX

فهرست مطالب

کار با APIها در جاوا اسکریپت: Fetch API و AJAX

در دنیای مدرن توسعه وب، تعامل پویا و بدون وقفه با سرورها و منابع داده، هسته اصلی تجربه کاربری روان و کارآمد را تشکیل می‌دهد. APIها (Application Programming Interfaces) این تعامل را ممکن می‌سازند و جاوا اسکریپت، به عنوان زبان برنامه‌نویسی اصلی سمت کلاینت، ابزارهای قدرتمندی را برای مدیریت این ارتباطات در اختیار توسعه‌دهندگان قرار می‌دهد. در این مقاله جامع، به بررسی عمیق دو رویکرد کلیدی در کار با APIها در جاوا اسکریپت خواهیم پرداخت: AJAX (مخفف Asynchronous JavaScript and XML) که با شیء XMLHttpRequest پیاده‌سازی می‌شود و Fetch API که رویکردی مدرن‌تر و مبتنی بر Promise را ارائه می‌دهد.

هدف این مقاله، ارائه یک راهنمای تخصصی و جامع برای توسعه‌دهندگانی است که به دنبال درک عمیق، مقایسه، و به‌کارگیری بهینه این دو فناوری در پروژه‌های خود هستند. ما نه تنها به مبانی و نحوه استفاده از هر یک خواهیم پرداخت، بلکه الگوهای پیشرفته، بهترین روش‌ها، مدیریت خطا، و ملاحظات امنیتی را نیز پوشش خواهیم داد تا شما را در ساخت اپلیکیشن‌های وب قدرتمند و مقیاس‌پذیر یاری کنیم.

درک مبانی APIها و پروتکل HTTP

پیش از ورود به جزئیات AJAX و Fetch، لازم است که فهم دقیقی از مفهوم API و پروتکل HTTP، که شالوده ارتباطات وب را تشکیل می‌دهد، داشته باشیم.

API چیست؟

API به مجموعه‌ای از توابع و روال‌ها گفته می‌شود که به برنامه‌های کاربردی اجازه می‌دهد تا با یکدیگر ارتباط برقرار کنند. در زمینه وب، APIها معمولاً به سرویس‌هایی اشاره دارند که از طریق پروتکل HTTP در دسترس قرار می‌گیرند و به برنامه‌های کلاینت (مانند مرورگرها یا اپلیکیشن‌های موبایل) اجازه می‌دهند تا به داده‌ها و قابلیت‌های یک سرور دسترسی پیدا کنند. رایج‌ترین نوع APIهای وب، RESTful APIها هستند که بر اساس اصول REST (Representational State Transfer) طراحی شده‌اند و از متدهای استاندارد HTTP برای انجام عملیات مختلف (مانند بازیابی، ایجاد، به‌روزرسانی، و حذف منابع) استفاده می‌کنند. ساختار داده در این APIها معمولاً به صورت JSON یا XML تبادل می‌شود.

پروتکل HTTP/HTTPS: زبان وب

HTTP (Hypertext Transfer Protocol) پروتکلی است که برای انتقال داده‌ها در وب استفاده می‌شود. HTTPS نسخه امن HTTP است که از رمزنگاری SSL/TLS برای حفاظت از داده‌ها در حین انتقال استفاده می‌کند. هر تعامل HTTP شامل یک درخواست (Request) از سمت کلاینت و یک پاسخ (Response) از سمت سرور است. اجزای اصلی یک درخواست و پاسخ HTTP عبارتند از:

  • متدهای HTTP (HTTP Methods/Verbs): این متدها نوع عملیاتی را که کلاینت می‌خواهد روی یک منبع انجام دهد، مشخص می‌کنند. رایج‌ترین متدها عبارتند از:
    • GET: برای بازیابی (خواندن) یک منبع از سرور.
    • POST: برای ایجاد (ارسال) یک منبع جدید به سرور.
    • PUT: برای به‌روزرسانی (جایگزینی کامل) یک منبع موجود.
    • DELETE: برای حذف یک منبع.
    • PATCH: برای به‌روزرسانی جزئی یک منبع.
    • HEAD: مشابه GET است اما فقط هدرها را برمی‌گرداند، بدون بدنه پاسخ.
    • OPTIONS: برای دریافت اطلاعات درباره متدهای HTTP پشتیبانی شده توسط سرور برای یک URL خاص.
  • آدرس URL: مکان منبع مورد نظر را روی سرور مشخص می‌کند.
  • هدرها (Headers): اطلاعات متادیتا درباره درخواست یا پاسخ را حمل می‌کنند، مانند نوع محتوا (Content-Type)، طول محتوا (Content-Length)، کوکی‌ها، توکن‌های احراز هویت (Authorization) و غیره.
  • بدنه (Body): شامل داده‌های واقعی است که در درخواست‌های POST، PUT و PATCH ارسال می‌شوند، یا داده‌هایی که در پاسخ‌های GET برگردانده می‌شوند (مثلاً یک شیء JSON).

کدهای وضعیت HTTP (HTTP Status Codes): سرور با یک کد وضعیت عددی به درخواست کلاینت پاسخ می‌دهد که نشان‌دهنده نتیجه عملیات است. دسته‌های اصلی کدها عبارتند از:

  • 1xx: اطلاعاتی (Informational)
  • 2xx: موفقیت (Success) – مثال: 200 OK، 201 Created، 204 No Content
  • 3xx: تغییر مسیر (Redirection) – مثال: 301 Moved Permanently
  • 4xx: خطاهای کلاینت (Client Error) – مثال: 400 Bad Request، 401 Unauthorized، 403 Forbidden، 404 Not Found
  • 5xx: خطاهای سرور (Server Error) – مثال: 500 Internal Server Error, 503 Service Unavailable

AJAX (Asynchronous JavaScript and XML): پیشگام ارتباطات ناهمزمان

پیش از ظهور AJAX، برای به‌روزرسانی محتوای یک صفحه وب، نیاز به بارگذاری مجدد کامل صفحه بود. این امر تجربه کاربری را مختل می‌کرد و باعث کندی می‌شد. در سال 2005، مفهوم AJAX معرفی شد و انقلابی در توسعه وب ایجاد کرد. AJAX به توسعه‌دهندگان این امکان را داد که بخش‌هایی از یک صفحه را بدون نیاز به بارگذاری مجدد کل صفحه، به‌روزرسانی کنند. این کار با استفاده از شیء XMLHttpRequest (XHR) که در جاوا اسکریپت مرورگرها تعبیه شده است، انجام می‌شود.

شیء XMLHttpRequest

XMLHttpRequest یک شیء API مرورگر است که امکان ارسال درخواست‌های HTTP به سرور و دریافت پاسخ‌های ناهمزمان را فراهم می‌کند. نام آن تاریخی است؛ در ابتدا عمدتاً برای تبادل داده‌های XML استفاده می‌شد، اما امروزه بیشتر با JSON کار می‌کند.

نحوه استفاده پایه از XMLHttpRequest

برای انجام یک درخواست GET با XMLHttpRequest:


function fetchDataWithXHR() {
    const xhr = new XMLHttpRequest();
    const url = 'https://api.example.com/data';

    // 1. تنظیم یک تابع برای رسیدگی به تغییرات وضعیت
    xhr.onreadystatechange = function() {
        if (xhr.readyState === XMLHttpRequest.DONE) {
            // 4. درخواست تکمیل شد
            if (xhr.status >= 200 && xhr.status < 300) {
                // 5. درخواست موفق بود
                try {
                    const data = JSON.parse(xhr.responseText);
                    console.log('Data received with XHR (GET):', data);
                    // اینجا می‌توانید داده‌ها را در DOM نمایش دهید
                } catch (e) {
                    console.error('Error parsing JSON:', e);
                }
            } else {
                // 6. درخواست با خطا مواجه شد
                console.error('XHR request failed with status:', xhr.status, xhr.statusText);
                console.error('Response text:', xhr.responseText);
            }
        }
    };

    // 2. باز کردن یک درخواست
    // متد GET، URL و true برای ناهمزمان بودن
    xhr.open('GET', url, true); 

    // 3. تنظیم هدرها (اختیاری)
    // xhr.setRequestHeader('Content-Type', 'application/json');
    // xhr.setRequestHeader('Authorization', 'Bearer YOUR_TOKEN');

    // 4. ارسال درخواست
    xhr.send();

    console.log('XHR GET request sent...');
}

// فراخوانی تابع
// fetchDataWithXHR();

توضیحات مراحل:

  1. new XMLHttpRequest(): یک شیء جدید XHR ایجاد می‌کند.
  2. xhr.onreadystatechange: این یک رویدادhandler است که هر زمان که ویژگی readyState شیء XHR تغییر کند، فراخوانی می‌شود.
  3. xhr.readyState: این ویژگی وضعیت فعلی درخواست را نشان می‌دهد. مقادیر آن عبارتند از:
    • 0 (UNSENT): درخواست هنوز باز نشده است.
    • 1 (OPENED): متد open() فراخوانی شده است.
    • 2 (HEADERS_RECEIVED): متد send() فراخوانی شده و هدرها و وضعیت در دسترس هستند.
    • 3 (LOADING): در حال دانلود responseText است؛ responseText تا حدی در دسترس است.
    • 4 (DONE): عملیات کامل شده است.

    ما معمولاً فقط به XMLHttpRequest.DONE (یا 4) اهمیت می‌دهیم تا مطمئن شویم درخواست به پایان رسیده است.

  4. xhr.open(method, url, async): این متد برای مقداردهی اولیه یک درخواست استفاده می‌شود.
    • method: متد HTTP مانند 'GET' یا 'POST'.
    • url: آدرس URL برای ارسال درخواست.
    • async: یک مقدار بولین که مشخص می‌کند درخواست باید ناهمزمان باشد (true) یا همزمان (false). تقریباً همیشه باید true باشد تا UI مسدود نشود.
  5. xhr.setRequestHeader(header, value): برای تنظیم هدرهای درخواست، مانند Content-Type یا Authorization، استفاده می‌شود. باید پس از open() و قبل از send() فراخوانی شود.
  6. xhr.send(body): درخواست را به سرور ارسال می‌کند. برای درخواست‌های GET یا HEAD، آرگومان body باید null باشد. برای POST یا PUT، شامل داده‌هایی است که باید ارسال شوند.
  7. xhr.status: کد وضعیت HTTP پاسخ (مثلاً 200 برای موفقیت، 404 برای یافت نشد، 500 برای خطای سرور).
  8. xhr.responseText: محتوای پاسخ سرور به صورت یک رشته متنی. اگر پاسخ JSON باشد، باید آن را با JSON.parse() تبدیل کنید.
  9. xhr.responseXML: محتوای پاسخ سرور به صورت یک شیء XML DOM (در صورتی که نوع محتوا XML باشد).

ارسال داده با POST توسط XMLHttpRequest


function postDataWithXHR() {
    const xhr = new XMLHttpRequest();
    const url = 'https://api.example.com/posts';
    const dataToSend = {
        title: 'My New Post',
        body: 'This is the content of my new post.',
        userId: 1
    };

    xhr.onreadystatechange = function() {
        if (xhr.readyState === XMLHttpRequest.DONE) {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    const responseData = JSON.parse(xhr.responseText);
                    console.log('Post created with XHR:', responseData);
                } catch (e) {
                    console.error('Error parsing JSON:', e);
                }
            } else {
                console.error('XHR POST request failed:', xhr.status, xhr.statusText);
            }
        }
    };

    xhr.open('POST', url, true);
    // بسیار مهم: تنظیم Content-Type برای ارسال JSON
    xhr.setRequestHeader('Content-Type', 'application/json');

    xhr.send(JSON.stringify(dataToSend)); // تبدیل شیء جاوا اسکریپت به رشته JSON
    console.log('XHR POST request sent...');
}

// postDataWithXHR();

مزایا و معایب XMLHttpRequest

مزایا:

  • پشتیبانی گسترده مرورگرها (سازگاری بالا حتی با مرورگرهای قدیمی).
  • کنترل دقیق بر درخواست‌ها (هدرها، Progress events و غیره).

معایب:

  • API قدیمی و verbose (پرگویی): نوشتن کدهای آن می‌تواند طولانی و پیچیده باشد، به خصوص برای درخواست‌های پیچیده یا زنجیره‌ای.
  • مدیریت callback hell: با افزایش تعداد درخواست‌های متوالی و وابسته به هم، کدهای callback می‌توانند به سرعت نامرتب و دشوار برای نگهداری شوند.
  • عدم پشتیبانی مستقیم از Promises: نیاز به بسته‌بندی دستی در Promiseها برای استفاده از async/await.
  • اینترفیس غیرطبیعی: طراحی آن به اندازه Fetch API مدرن و JavaScript-friendly نیست.

ورود Fetch API: نسل جدید درخواست‌های وب

Fetch API، که در ES6 (ECMAScript 2015) معرفی شد، یک جایگزین مدرن و قدرتمند برای XMLHttpRequest است. این API مبتنی بر Promise است و یک اینترفیس تمیزتر و منعطف‌تر برای ساخت درخواست‌های HTTP ارائه می‌دهد. Fetch API به طور ذاتی با ویژگی‌های جدید جاوا اسکریپت مانند async/await سازگاری دارد که مدیریت کد ناهمزمان را بسیار ساده‌تر می‌کند.

مقایسه Fetch با XMLHttpRequest

  • مبتنی بر Promise: Fetch به طور طبیعی Promiseها را برمی‌گرداند که اجازه می‌دهد از .then() و .catch() برای مدیریت موفقیت و شکست استفاده کرد و با async/await کار کند. XHR مبتنی بر event handlerها و callbackها است.
  • سینتکس تمیزتر: کد Fetch به طور قابل توجهی کوتاه‌تر و خواناتر از XHR است.
  • پشتیبانی از Stream: Fetch می‌تواند با استریم‌های داده کار کند که برای حجم‌های بزرگ داده کارآمدتر است.
  • پیش‌فرض No-CORS: Fetch به صورت پیش‌فرض با حالت no-cors کار می‌کند که می‌تواند برای درخواست به دامنه‌های دیگر مشکل‌ساز باشد مگر اینکه سرور هدرهای CORS مناسب را ارسال کند. XHR این محدودیت را ندارد مگر اینکه به طور صریح غیرفعال شود.
  • مدیریت خطا: Fetch تنها زمانی Promise را reject می‌کند که یک خطای شبکه رخ دهد (مانند عدم دسترسی به اینترنت). کدهای وضعیت HTTP مانند 404 یا 500 منجر به reject شدن Promise نمی‌شوند؛ در این حالت Promise resolve می‌شود اما با یک پاسخ که ویژگی response.ok آن false است. این یک تفاوت مهم با XHR است که در صورت دریافت کد وضعیت 4xx/5xx، خطای XHR را نشان می‌دهد.

نحوه استفاده پایه از Fetch API

یک درخواست GET ساده با Fetch:


function fetchDataWithFetch() {
    const url = 'https://api.example.com/data';

    fetch(url)
        .then(response => {
            // بررسی وضعیت HTTP
            if (!response.ok) {
                // اگر کد وضعیت 2xx نباشد، خطا پرتاب کن
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            // تبدیل پاسخ به JSON
            return response.json();
        })
        .then(data => {
            console.log('Data received with Fetch (GET):', data);
            // اینجا می‌توانید داده‌ها را در DOM نمایش دهید
        })
        .catch(error => {
            // مدیریت خطاهای شبکه یا خطاهای پرتاب شده در .then()
            console.error('Fetch GET request failed:', error);
        });

    console.log('Fetch GET request sent...');
}

// فراخوانی تابع
// fetchDataWithFetch();

در مثال بالا، fetch(url) یک Promise را برمی‌گرداند. اولین .then() با شیء Response حل می‌شود. ما ابتدا response.ok را بررسی می‌کنیم تا مطمئن شویم درخواست HTTP موفقیت‌آمیز بوده است (کد وضعیت 200-299). سپس response.json() را فراخوانی می‌کنیم که خود یک Promise دیگر را برمی‌گرداند که محتوای بدنه پاسخ را به صورت یک شیء جاوا اسکریپت تجزیه می‌کند. .catch() برای گرفتن خطاهای شبکه (مانند قطع شدن اینترنت) یا هر خطایی که در داخل .then()ها پرتاب شود، استفاده می‌شود.

ارسال داده با POST توسط Fetch API


function postDataWithFetch() {
    const url = 'https://api.example.com/posts';
    const dataToSend = {
        title: 'My New Post with Fetch',
        body: 'This is the content of my new post using Fetch API.',
        userId: 1
    };

    fetch(url, {
        method: 'POST', // متد HTTP
        headers: {
            'Content-Type': 'application/json', // نوع محتوای ارسالی
            'Authorization': 'Bearer YOUR_TOKEN_HERE' // مثال: هدر احراز هویت
        },
        body: JSON.stringify(dataToSend) // تبدیل شیء جاوا اسکریپت به رشته JSON
    })
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        console.log('Post created with Fetch:', data);
    })
    .catch(error => {
        console.error('Fetch POST request failed:', error);
    });

    console.log('Fetch POST request sent...');
}

// postDataWithFetch();

برای درخواست‌های POST، PUT، DELETE یا PATCH، آرگومان دوم fetch() یک شیء پیکربندی است که شامل موارد زیر است:

  • method: متد HTTP مورد نظر.
  • headers: یک شیء شامل هدرهای درخواست.
  • body: داده‌هایی که باید ارسال شوند. برای JSON، باید از JSON.stringify() استفاده شود.

شیء Request و Response

Fetch API بر اساس مفاهیم اصلی شیء Request و Response ساخته شده است. این اشیاء نمایانگر بخش‌های پروتکل HTTP هستند:

  • Request: یک شیء Request نمایانگر یک درخواست HTTP است. شما می‌توانید یک شیء Request را به صورت دستی ایجاد کنید و آن را به fetch() ارسال کنید تا کنترل دقیق‌تری بر درخواست داشته باشید.
    
    const myRequest = new Request('https://api.example.com/data', {
        method: 'GET',
        headers: {
            'Accept': 'application/json'
        },
        cache: 'no-store' // مثال: جلوگیری از کش شدن
    });
    
    fetch(myRequest)
        .then(response => response.json())
        .then(data => console.log('Data from Request object:', data))
        .catch(error => console.error('Error with Request object:', error));
            
  • Response: یک شیء Response نمایانگر پاسخ به یک درخواست HTTP است. این شیء حاوی اطلاعاتی مانند کد وضعیت (status)، متن وضعیت (statusText)، هدرها (headers) و متدهای مختلفی برای دسترسی به بدنه پاسخ (json(), text(), blob() و غیره) است.

لغو درخواست‌ها با AbortController

یکی از قابلیت‌های مهم در مدیریت درخواست‌های شبکه، امکان لغو (Abort) یک درخواست در حال انجام است. این امر به خصوص در سناریوهایی که کاربر درخواست جدیدی را قبل از تکمیل درخواست قبلی ارسال می‌کند (مانند جستجوهای تایپ همزمان)، مفید است. Fetch API از AbortController برای این منظور استفاده می‌کند:


let controller; // کنترلر را در یک دامنه بالاتر تعریف کنید تا بتوانید آن را در هر زمان لغو کنید

function cancelableFetch() {
    // اگر درخواست قبلی در حال انجام بود، آن را لغو کنید
    if (controller) {
        controller.abort();
        console.log('Previous request aborted.');
    }
    
    controller = new AbortController();
    const signal = controller.signal; // سیگنال برای AbortController

    const url = 'https://api.example.com/long-running-task';

    fetch(url, { signal }) // ارسال سیگنال به fetch
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            console.log('Data received:', data);
            controller = null; // درخواست با موفقیت تکمیل شد
        })
        .catch(error => {
            if (error.name === 'AbortError') {
                console.warn('Fetch request was aborted.');
            } else {
                console.error('Fetch error:', error);
            }
            controller = null; // درخواست با خطا یا لغو به پایان رسید
        });

    console.log('New fetch request started...');
}

// برای مثال، هر 2 ثانیه یکبار فراخوانی کنید
// setInterval(cancelableFetch, 2000);

// برای لغو دستی:
// setTimeout(() => {
//     if (controller) {
//         controller.abort();
//         console.log('Manually aborted the request!');
//     }
// }, 1000);

مدیریت خطا و حالات بارگذاری در API Calls

مدیریت صحیح خطاها و بازخورد به کاربر درباره وضعیت بارگذاری داده‌ها، بخش حیاتی از توسعه اپلیکیشن‌های وب کاربرپسند و مقاوم است.

استراتژی‌های مدیریت خطا

  1. خطاهای شبکه در مقابل خطاهای HTTP:
    • خطاهای شبکه (Network Errors): این خطاها زمانی رخ می‌دهند که درخواست اصلاً به سرور نمی‌رسد یا اتصال قطع می‌شود (مانند عدم اتصال به اینترنت، مشکلات DNS). Fetch Promise را reject می‌کند. XHR نیز یک خطای 'error' را fire می‌کند. این نوع خطاها معمولاً با پیام‌هایی مانند "Failed to fetch" یا "Network Error" نمایش داده می‌شوند.
    • خطاهای HTTP (HTTP Errors): این خطاها زمانی رخ می‌دهند که درخواست به سرور می‌رسد اما سرور با کد وضعیت خطایی (مانند 4xx یا 5xx) پاسخ می‌دهد. Fetch Promise را resolve می‌کند (زیرا درخواست شبکه موفق بوده است)، اما ویژگی response.ok روی false تنظیم می‌شود. بنابراین، شما باید صراحتاً response.ok را بررسی کنید و در صورت نیاز یک خطا throw کنید تا به بلاک .catch() برسد. XHR در این حالت وضعیت status را برمی‌گرداند و onreadystatechange را فراخوانی می‌کند.
  2. استفاده از try...catch با async/await:

    async/await کد ناهمزمان را به گونه‌ای می‌نویسد که شبیه کد همزمان به نظر برسد و مدیریت خطا را با try...catch بسیار ساده می‌کند:

    
    async function fetchDataAsync() {
        const url = 'https://api.example.com/data';
        try {
            const response = await fetch(url);
    
            if (!response.ok) {
                const errorBody = await response.text(); // یا response.json()
                throw new Error(`HTTP error! Status: ${response.status}. Message: ${errorBody}`);
            }
    
            const data = await response.json();
            console.log('Data received (async/await):', data);
            // نمایش داده
        } catch (error) {
            console.error('Failed to fetch data (async/await):', error);
            // نمایش پیام خطا به کاربر
            if (error.name === 'TypeError' || error.message.includes('Failed to fetch')) {
                console.error('Network error or CORS issue');
            } else if (error.message.includes('HTTP error!')) {
                console.error('Server returned an error status:', error.message);
            }
            // می‌توانید بسته به نوع خطا پیام‌های مختلفی نشان دهید
        }
    }
    
    // fetchDataAsync();
            

    استفاده از async/await به شدت توصیه می‌شود زیرا خوانایی کد را به طرز چشمگیری بهبود می‌بخشد و خطاهای پرتاب شده را به صورت معمول، در بلاک catch مدیریت می‌کند، بدون نیاز به زنجیره‌های .then().catch() طولانی.

  3. اطلاعات خطا از سرور: در بسیاری از APIها، سرور در صورت بروز خطا (مثلاً 400 Bad Request یا 404 Not Found)، یک شیء JSON در بدنه پاسخ ارسال می‌کند که حاوی جزئیات بیشتری درباره خطا است. همیشه سعی کنید این اطلاعات را از response.json() یا response.text() بخوانید تا پیام خطای معنی‌دارتری به کاربر نمایش دهید.

نمایش حالات بارگذاری و بازخورد به کاربر

هنگامی که داده‌ها از سرور واکشی می‌شوند، ممکن است تأخیری وجود داشته باشد. برای بهبود تجربه کاربری، باید بازخورد بصری مناسبی به کاربر ارائه دهید:

  1. اسپینرها یا لودینگ ایندیکیتورها: قبل از شروع درخواست، یک اسپینر یا پیام "در حال بارگذاری..." را نمایش دهید و پس از دریافت پاسخ (چه موفق و چه ناموفق)، آن را پنهان کنید.
  2. Skeleton Screens: به جای اسپینر، می‌توانید از Skeleton Screens استفاده کنید که طرح کلی محتوای در حال بارگذاری را نشان می‌دهند و تجربه بصری روان‌تری را ارائه می‌دهند.
  3. غیرفعال کردن دکمه‌ها: در حین ارسال داده (مثلاً هنگام ارسال فرم)، دکمه ارسال را غیرفعال کنید تا از ارسال چندباره جلوگیری شود و کاربر متوجه شود که عملیات در حال انجام است.
  4. پیام‌های خطا و موفقیت: پس از اتمام درخواست، یک پیام مختصر و واضح (مثلاً در یک توست یا نوار اعلان) درباره موفقیت یا شکست عملیات نمایش دهید.

<div id="loadingIndicator" style="display: none;">در حال بارگذاری...</div>
<button id="fetchButton">واکشی داده</button>
<div id="dataContainer"></div>
<div id="errorMessage" style="color: red;"></div>

<script>
    const loadingIndicator = document.getElementById('loadingIndicator');
    const fetchButton = document.getElementById('fetchButton');
    const dataContainer = document.getElementById('dataContainer');
    const errorMessage = document.getElementById('errorMessage');

    async function handleFetchClick() {
        loadingIndicator.style.display = 'block';
        fetchButton.disabled = true;
        dataContainer.innerHTML = '';
        errorMessage.innerHTML = '';

        try {
            const response = await fetch('https://api.example.com/data');
            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(`خطای HTTP! وضعیت: ${response.status}. ${errorText}`);
            }
            const data = await response.json();
            dataContainer.innerHTML = '<p>داده‌ها با موفقیت دریافت شدند:</p><pre><code>' + JSON.stringify(data, null, 2) + '</code></pre>';
        } catch (error) {
            errorMessage.innerHTML = '<p>خطا در واکشی داده‌ها: ' + error.message + '</p>';
            console.error('Fetch error:', error);
        } finally {
            loadingIndicator.style.display = 'none';
            fetchButton.disabled = false;
        }
    }

    fetchButton.addEventListener('click', handleFetchClick);
</script>

مدیریت Race Conditions (شرایط رقابت)

Race condition زمانی اتفاق می‌افتد که ترتیب پاسخ‌دهی چندین درخواست ناهمزمان، غیرقابل پیش‌بینی باشد و ممکن است منجر به نمایش داده‌های نادرست شود. به عنوان مثال، اگر کاربر به سرعت دو بار روی دکمه "جستجو" کلیک کند، ممکن است پاسخ درخواست دوم زودتر از پاسخ درخواست اول برسد و نتیجه جستجوی اول را به اشتباه نمایش دهد.

راه‌حل‌ها:

  • لغو درخواست‌های قبلی (AbortController): همانطور که قبلاً نشان داده شد، قبل از شروع درخواست جدید، درخواست‌های قبلی را لغو کنید.
  • Debouncing/Throttling: از توابع Debounce یا Throttle برای محدود کردن تعداد فراخوانی‌های تابع ارسال درخواست در یک بازه زمانی مشخص استفاده کنید.
  • نادیده گرفتن پاسخ‌های قدیمی: یک counter یا timestamp را با هر درخواست ارسال کنید و تنها در صورتی پاسخ را پردازش کنید که مربوط به آخرین درخواست ارسال شده باشد.

الگوهای پیشرفته و بهترین روش‌ها

Async/Await: مدیریت ساده‌تر Promiseها

async/await یک سینتکس ویژه در جاوا اسکریپت است که کار با Promiseها را بسیار ساده‌تر و کد ناهمزمان را شبیه کد همزمان می‌کند. یک تابع async همیشه یک Promise برمی‌گرداند و کلمه کلیدی await می‌تواند فقط در داخل یک تابع async استفاده شود و اجرای تابع را متوقف می‌کند تا Promise مورد انتظار resolve شود.

مثال کامل‌تر با async/await برای GET و POST:


async function fetchAndPostExample() {
    const baseUrl = 'https://jsonplaceholder.typicode.com'; // یک API تستی رایگان

    // === GET Request ===
    try {
        console.log('Fetching posts...');
        const postsResponse = await fetch(`${baseUrl}/posts?_limit=5`); // درخواست 5 پست اول

        if (!postsResponse.ok) {
            throw new Error(`HTTP error! status: ${postsResponse.status}`);
        }
        const posts = await postsResponse.json();
        console.log('Fetched Posts:', posts);
    } catch (error) {
        console.error('Error fetching posts:', error);
    }

    // === POST Request ===
    const newPost = {
        title: 'New Post Title from Fetch/Async',
        body: 'This is the body of the new post created with async/await and Fetch.',
        userId: 1
    };

    try {
        console.log('Creating a new post...');
        const postResponse = await fetch(`${baseUrl}/posts`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json; charset=UTF-8'
            },
            body: JSON.stringify(newPost)
        });

        if (!postResponse.ok) {
            throw new Error(`HTTP error! status: ${postResponse.status}`);
        }
        const createdPost = await postResponse.json();
        console.log('Created Post:', createdPost);
    } catch (error) {
        console.error('Error creating post:', error);
    }
}

// fetchAndPostExample();

سریال‌سازی و دسیریال‌سازی داده‌ها (JSON)

در وب مدرن، JSON (JavaScript Object Notation) رایج‌ترین فرمت برای تبادل داده‌ها بین کلاینت و سرور است. جاوا اسکریپت توابع داخلی برای کار با JSON دارد:

  • JSON.stringify(object): یک شیء یا مقدار جاوا اسکریپت را به یک رشته JSON تبدیل می‌کند. این برای ارسال داده‌ها به سرور در بدنه درخواست (body) استفاده می‌شود.
  • JSON.parse(jsonString): یک رشته JSON را تجزیه کرده و یک شیء یا مقدار جاوا اسکریپت را برمی‌گرداند. این برای تبدیل پاسخ سرور به یک شیء قابل استفاده در جاوا اسکریپت استفاده می‌شود. (Fetch API با response.json() این کار را به صورت خودکار انجام می‌دهد.)

احراز هویت (Authentication)

دسترسی به بسیاری از APIها نیاز به احراز هویت دارد تا سرور هویت کلاینت را شناسایی و مجوزهای دسترسی آن را بررسی کند. روش‌های رایج احراز هویت در APIهای وب شامل:

  • API Keys: یک رشته منحصر به فرد که در هدر یا به عنوان یک پارامتر query در URL ارسال می‌شود. (کمتر امن)
  • توکن‌های مبتنی بر JWT (JSON Web Tokens): پس از ورود کاربر، سرور یک JWT صادر می‌کند. این توکن در هر درخواست بعدی در هدر Authorization به صورت Bearer YOUR_TOKEN ارسال می‌شود. این رایج‌ترین روش در SPAها است.
  • OAuth 2.0: یک فریم‌ورک احراز هویت برای دسترسی امن و محدود به منابع کاربر بدون نیاز به افشای اطلاعات ورود کاربر. این معمولاً برای لاگین با شبکه‌های اجتماعی (مثل Google، Facebook) یا اعطای دسترسی به برنامه‌های شخص ثالث استفاده می‌شود.

مثال ارسال توکن JWT در هدر:


async function fetchWithAuth() {
    const token = 'YOUR_JWT_TOKEN_HERE'; // توکن JWT دریافت شده پس از ورود
    const url = 'https://api.example.com/protected-data';

    try {
        const response = await fetch(url, {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${token}`, // ارسال توکن در هدر Authorization
                'Content-Type': 'application/json'
            }
        });

        if (response.status === 401) {
            console.error('Unauthorized: Invalid or expired token');
            // ریدایرکت به صفحه ورود یا نمایش پیام
            return;
        }

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        console.log('Protected Data:', data);
    } catch (error) {
        console.error('Error fetching protected data:', error);
    }
}

// fetchWithAuth();

کشینگ (Caching)

کشینگ به ذخیره پاسخ‌های API در کلاینت (مرورگر) برای کاهش تعداد درخواست‌های شبکه و بهبود عملکرد کمک می‌کند. روش‌های مختلفی برای مدیریت کشینگ وجود دارد:

  • کشینگ HTTP (Headers): سرور می‌تواند هدرهایی مانند Cache-Control، Expires و ETag را ارسال کند که به مرورگر می‌گوید چگونه پاسخ را کش کند.
    • Cache-Control: public, max-age=3600: به مرورگر می‌گوید پاسخ را برای یک ساعت کش کند.
    • ETag: یک شناسه منحصر به فرد برای نسخه فعلی منبع. اگر مرورگر یک ETag ذخیره شده داشته باشد، می‌تواند در درخواست بعدی هدر If-None-Match را ارسال کند. سرور اگر منبع تغییر نکرده باشد، با 304 Not Modified پاسخ می‌دهد.
  • کشینگ سمت کلاینت (جاوا اسکریپت): می‌توانید داده‌ها را در localStorage، sessionStorage یا IndexedDB ذخیره کنید.
    
    async function fetchDataWithClientCache(url, cacheKey) {
        const cachedData = localStorage.getItem(cacheKey);
        if (cachedData) {
            console.log('Data fetched from cache:', JSON.parse(cachedData));
            // بررسی تاریخ انقضا و به‌روزرسانی در پس‌زمینه
            // return JSON.parse(cachedData);
        }
    
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error('Network response was not ok.');
            const data = await response.json();
            localStorage.setItem(cacheKey, JSON.stringify(data)); // ذخیره در کش
            console.log('Data fetched from network and cached:', data);
            return data;
        } catch (error) {
            console.error('Error fetching data:', error);
            if (cachedData) {
                return JSON.parse(cachedData); // در صورت خطا، داده کش شده را برگردان
            }
            throw error;
        }
    }
    
    // fetchDataWithClientCache('https://api.example.com/products', 'productsCache');
            
  • Service Workers: برای کنترل پیشرفته‌تر کشینگ و ارائه قابلیت‌های آفلاین.

محدودیت نرخ (Rate Limiting)

بسیاری از APIها محدودیتی در تعداد درخواست‌هایی که می‌توانید در یک بازه زمانی مشخص ارسال کنید، اعمال می‌کنند. اگر از این محدودیت فراتر روید، سرور معمولاً با کد وضعیت 429 Too Many Requests پاسخ می‌دهد و ممکن است هدر Retry-After را نیز ارسال کند که نشان می‌دهد چه زمانی می‌توانید درخواست بعدی را ارسال کنید. باید کدهای خود را طوری طراحی کنید که این پاسخ‌ها را شناسایی کرده و به صورت مناسب مدیریت کنند (مثلاً با مکانیزم "exponential backoff" که درخواست‌ها را با تاخیر فزاینده‌ای دوباره ارسال می‌کند).

ملاحظات امنیتی

  • Cross-Site Scripting (XSS): هرگز داده‌های دریافت شده از API را مستقیماً بدون sanitization در DOM قرار ندهید. همیشه داده‌ها را پیش از نمایش از نظر اسکریپت‌های مخرب بررسی و پاکسازی کنید.
  • Cross-Site Request Forgery (CSRF): اطمینان حاصل کنید که درخواست‌های POST، PUT و DELETE شما شامل توکن‌های CSRF باشند (که سرور تولید می‌کند و باید در هر درخواست ارسال شود) تا از حملات CSRF جلوگیری شود.
  • HTTPS: همیشه از HTTPS برای تمام ارتباطات API استفاده کنید تا داده‌ها در حین انتقال رمزنگاری شوند.
  • پنهان نگه داشتن اعتبارنامه‌های حساس: کلیدهای API حساس یا اعتبارنامه‌ها را در کد سمت کلاینت ذخیره نکنید. آنها را در سمت سرور مدیریت کنید.

طراحی توابع/ماژول‌های کلاینت API قابل استفاده مجدد

برای مدیریت بهتر کد و افزایش قابلیت استفاده مجدد، بهتر است یک ماژول یا کلاس جداگانه برای تعامل با APIهای خود ایجاد کنید. این کار به شما امکان می‌دهد:

  • مرکزی‌سازی URLهای پایه: تمام URLهای API را در یک مکان تعریف کنید.
  • مدیریت هدرهای مشترک: هدرهایی مانند Content-Type یا Authorization را به صورت خودکار به تمام درخواست‌ها اضافه کنید.
  • مدیریت خطای مرکزی: یک منطق مدیریت خطا برای تمام درخواست‌ها پیاده‌سازی کنید.

class ApiClient {
    constructor(baseUrl, defaultHeaders = {}) {
        this.baseUrl = baseUrl;
        this.defaultHeaders = {
            'Content-Type': 'application/json',
            ...defaultHeaders
        };
    }

    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        const config = {
            ...options,
            headers: {
                ...this.defaultHeaders,
                ...options.headers
            }
        };

        try {
            const response = await fetch(url, config);

            if (!response.ok) {
                let errorDetails = `HTTP error! Status: ${response.status}`;
                try {
                    const errorJson = await response.json();
                    errorDetails += ` - ${errorJson.message || JSON.stringify(errorJson)}`;
                } catch (e) {
                    // اگر پاسخ JSON نبود
                    errorDetails += ` - ${await response.text()}`;
                }
                throw new Error(errorDetails);
            }

            // اگر پاسخ NoContent باشد (204)
            if (response.status === 204) {
                return null;
            }

            return response.json();
        } catch (error) {
            console.error('API Client Error:', error);
            throw error; // خطا را دوباره پرتاب کن تا توسط کدهای فراخوانی کننده مدیریت شود
        }
    }

    get(endpoint, params = {}) {
        const queryString = new URLSearchParams(params).toString();
        const fullEndpoint = queryString ? `${endpoint}?${queryString}` : endpoint;
        return this.request(fullEndpoint, { method: 'GET' });
    }

    post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }

    put(endpoint, data) {
        return this.request(endpoint, {
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }

    delete(endpoint) {
        return this.request(endpoint, { method: 'DELETE' });
    }

    setAuthToken(token) {
        this.defaultHeaders['Authorization'] = `Bearer ${token}`;
    }

    removeAuthToken() {
        delete this.defaultHeaders['Authorization'];
    }
}

// مثال استفاده:
// const myApiClient = new ApiClient('https://api.example.com');
// myApiClient.setAuthToken('your_jwt_token_here');

// async function testApiClient() {
//     try {
//         const users = await myApiClient.get('/users', { limit: 10 });
//         console.log('Users:', users);

//         const newUser = await myApiClient.post('/users', { name: 'Ali', email: 'ali@example.com' });
//         console.log('New User:', newUser);

//         await myApiClient.delete('/users/123'); // فرض کنید id کاربر 123 است
//         console.log('User 123 deleted.');
//     } catch (error) {
//         console.error('Error in API client usage:', error);
//     }
// }

// testApiClient();

ابزارها و کتابخانه‌های کمکی

در حالی که Fetch API ابزاری قدرتمند است، کتابخانه‌های جانبی نیز برای ساده‌سازی بیشتر کار با APIها و ارائه قابلیت‌های اضافی توسعه یافته‌اند.

Axios: یک جایگزین محبوب برای Fetch

Axios یک کلاینت HTTP مبتنی بر Promise برای مرورگر و Node.js است. در بین توسعه‌دهندگان بسیار محبوب است و بسیاری از ویژگی‌هایی را ارائه می‌دهد که Fetch API به صورت Native ندارد یا پیاده‌سازی آن‌ها پیچیده‌تر است.

مزایای Axios نسبت به Fetch API:

  • پشتیبانی از مرورگرهای قدیمی‌تر: Axios به صورت خودکار از XMLHttpRequest برای مرورگرهای قدیمی‌تر که Fetch API را پشتیبانی نمی‌کنند، استفاده می‌کند.
  • قابلیت لغو درخواست‌ها (Cancellations): دارای API داخلی برای لغو درخواست‌ها (برخلاف Fetch که نیاز به AbortController جداگانه دارد).
  • اینترسپتورها (Interceptors): اینترسپتورها به شما اجازه می‌دهند تا درخواست‌ها یا پاسخ‌ها را قبل از اینکه توسط .then() یا .catch() مدیریت شوند، رهگیری و تغییر دهید. این برای افزودن هدرهای احراز هویت به تمام درخواست‌ها، لاگ کردن درخواست‌ها، یا مدیریت خطاهای عمومی (مانند ریدایرکت هنگام 401) بسیار مفید است.
  • تبدیل خودکار JSON: Axios به طور خودکار داده‌های JSON را در درخواست‌ها (serializes) و پاسخ‌ها (deserializes) تبدیل می‌کند، بنابراین نیازی به JSON.stringify() یا response.json() نیست.
  • مدیریت خطا بهتر: Axios در صورت دریافت کدهای وضعیت 4xx/5xx، Promise را reject می‌کند که با نحوه مدیریت خطاهای سنتی جاوا اسکریپت سازگارتر است.
  • گزارش پیشرفت (Progress Reporting): امکان دسترسی به Progress events برای آپلود و دانلود.
  • امنیت (XSRF Protection): به صورت پیش‌فرض از Cross-Site Request Forgery (XSRF) محافظت می‌کند.

مثال Axios:


// نیاز به نصب: npm install axios یا yarn add axios
// import axios from 'axios';

function fetchDataWithAxios() {
    axios.get('https://api.example.com/data')
        .then(response => {
            // داده‌ها به صورت خودکار JSON.parsed شده‌اند
            console.log('Data received with Axios (GET):', response.data);
            console.log('Status:', response.status);
            console.log('Headers:', response.headers);
        })
        .catch(error => {
            if (error.response) {
                // سرور پاسخ داد اما با کد وضعیت خارج از محدوده 2xx
                console.error('Axios error response:', error.response.data);
                console.error('Axios error status:', error.response.status);
                console.error('Axios error headers:', error.response.headers);
            } else if (error.request) {
                // درخواست ارسال شد اما پاسخی دریافت نشد (خطای شبکه)
                console.error('Axios error request (No response received):', error.request);
            } else {
                // خطاهای دیگری که در تنظیم درخواست رخ دادند
                console.error('Axios error message:', error.message);
            }
            console.error('Axios config:', error.config);
        });
}

function postDataWithAxios() {
    const dataToSend = {
        title: 'Axios Post',
        body: 'This post was sent with Axios.',
        userId: 1
    };

    axios.post('https://api.example.com/posts', dataToSend, {
        headers: {
            'Authorization': 'Bearer YOUR_TOKEN'
        }
    })
    .then(response => {
        console.log('Post created with Axios:', response.data);
    })
    .catch(error => {
        console.error('Axios POST error:', error);
    });
}

// fetchDataWithAxios();
// postDataWithAxios();

jQuery AJAX (تاریخی)

پیش از ظهور Fetch API و محبوبیت Axios، jQuery AJAX یکی از رایج‌ترین راه‌ها برای انجام درخواست‌های ناهمزمان در جاوا اسکریپت بود. این ابزار یک انتزاع ساده‌تر بر روی XMLHttpRequest ارائه می‌داد و پیچیدگی‌های آن را پنهان می‌کرد.

مثال jQuery AJAX (فقط برای شناخت، استفاده آن در پروژه‌های جدید توصیه نمی‌شود):


// نیاز به کتابخانه jQuery
// $.ajax({
//     url: 'https://api.example.com/data',
//     method: 'GET',
//     dataType: 'json', // نوع داده مورد انتظار پاسخ
//     success: function(data) {
//         console.log('Data received with jQuery AJAX:', data);
//     },
//     error: function(jqXHR, textStatus, errorThrown) {
//         console.error('jQuery AJAX error:', textStatus, errorThrown, jqXHR.responseText);
//     }
// });

با وجود سادگی، jQuery AJAX نیز از مدل callback استفاده می‌کرد و با ظهور Promises و Fetch API، استفاده از آن در پروژه‌های جدید جاوا اسکریپت مدرن کمتر توصیه می‌شود.

ابزارهای تست API

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

  • Postman: یک پلتفرم کامل برای طراحی، تست، و مستندسازی APIها. بسیار قدرتمند و پرکاربرد است.
  • Insomnia: جایگزینی مشابه Postman با رابط کاربری تمیز و ساده.
  • Browser Developer Tools: تب "Network" در ابزارهای توسعه‌دهنده مرورگر (Chrome DevTools, Firefox Developer Tools) ابزاری ضروری برای مشاهده و اشکال‌زدایی تمام درخواست‌های HTTP است که از صفحه شما ارسال و دریافت می‌شوند. می‌توانید هدرها، بدنه درخواست/پاسخ، وضعیت، زمان‌بندی و غیره را بررسی کنید.

سناریوهای عملی و مثال‌های پیچیده‌تر

آپلود فایل با Fetch API و FormData

برای آپلود فایل‌ها، باید از شیء FormData استفاده کنید که داده‌ها را در قالب multipart/form-data کپسوله می‌کند، درست مانند یک فرم HTML معمولی:


async function uploadFile(file) {
    const url = 'https://api.example.com/upload';
    const formData = new FormData();
    formData.append('profileImage', file, file.name); // 'profileImage' نام فیلد در سمت سرور

    try {
        const response = await fetch(url, {
            method: 'POST',
            // نیازی به تنظیم Content-Type نیست؛ Fetch و FormData آن را به درستی تنظیم می‌کنند
            body: formData
        });

        if (!response.ok) {
            throw new Error(`Upload failed! Status: ${response.status}`);
        }

        const result = await response.json();
        console.log('File upload successful:', result);
    } catch (error) {
        console.error('Error uploading file:', error);
    }
}

// مثال استفاده:
// const fileInput = document.createElement('input');
// fileInput.type = 'file';
// document.body.appendChild(fileInput);

// fileInput.addEventListener('change', (event) => {
//     const selectedFile = event.target.files[0];
//     if (selectedFile) {
//         uploadFile(selectedFile);
//     }
// });

بروزرسانی‌های Real-time (Real-time Updates)

برای سناریوهایی که نیاز به به‌روزرسانی‌های لحظه‌ای از سرور دارند (مانند چت، نوتیفیکیشن‌ها، داشبوردهای زنده)، درخواست‌های HTTP سنتی (AJAX/Fetch) به تنهایی کافی نیستند. در این موارد، از فناوری‌هایی مانند:

  • WebSockets: یک پروتکل ارتباطی دوطرفه و تمام‌دوطرفه بر بستر TCP که به سرور اجازه می‌دهد داده‌ها را به کلاینت push کند و بالعکس، بدون نیاز به درخواست‌های مکرر.
  • Server-Sent Events (SSE): یک پروتکل یک‌طرفه که به سرور اجازه می‌دهد داده‌ها را به کلاینت push کند. ساده‌تر از WebSockets است اما فقط برای دریافت داده از سرور مناسب است.
  • Polling/Long Polling: Polling شامل ارسال درخواست‌های GET مکرر در فواصل زمانی ثابت است. Long Polling نیز شامل ارسال درخواست می‌شود، اما سرور تا زمانی که داده‌ای برای ارسال نداشته باشد، پاسخ را باز نگه می‌دارد. این روش‌ها نسبت به WebSockets و SSE ناکارآمدتر هستند اما در برخی موارد ممکن است تنها گزینه باشند.

در حالی که AJAX و Fetch API برای Polling استفاده می‌شوند، برای راه‌حل‌های Real-time واقعی بهتر است به سراغ WebSockets یا SSE بروید.

صفحه‌بندی (Pagination)

هنگام کار با حجم زیادی از داده‌ها، دریافت تمام آن‌ها در یک درخواست ناکارآمد و غیرممکن است. صفحه‌بندی به شما اجازه می‌دهد تا داده‌ها را در "صفحات" یا "قطعات" کوچک‌تر درخواست کنید. این کار معمولاً با ارسال پارامترهای query مانند page و limit (یا offset) به API انجام می‌شود.


async function fetchPaginatedData(page = 1, limit = 10) {
    const url = `https://api.example.com/items?page=${page}&limit=${limit}`;
    try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Failed to fetch paginated data.');
        const data = await response.json();
        console.log(`Page ${page} data:`, data);
        // نمایش داده‌ها و کنترل دکمه‌های صفحه‌بندی (بعدی/قبلی)
        return data;
    } catch (error) {
        console.error('Error fetching paginated data:', error);
    }
}

// fetchPaginatedData(1, 20); // درخواست صفحه 1 با 20 آیتم
// fetchPaginatedData(2, 20); // درخواست صفحه 2

واکشی همزمان چندین درخواست (Concurrent Requests)

در برخی موارد، ممکن است نیاز باشد چندین درخواست API را به صورت همزمان ارسال کنید و زمانی که تمام آن‌ها پاسخ دادند، نتایج را پردازش کنید. جاوا اسکریپت برای این کار توابعی در Promise ارائه می‌دهد:

  • Promise.all(iterable): آرایه‌ای از Promiseها را می‌گیرد و یک Promise جدید را برمی‌گرداند. این Promise جدید زمانی resolve می‌شود که تمام Promiseهای موجود در آرایه resolve شوند. اگر حتی یکی از Promiseها reject شود، Promise.all فوراً reject می‌شود.
  • Promise.allSettled(iterable): آرایه‌ای از Promiseها را می‌گیرد و یک Promise را برمی‌گرداند که زمانی resolve می‌شود که تمام Promiseهای ورودی settled (یعنی یا fulfilled یا rejected) شده باشند. این برای سناریوهایی مفید است که می‌خواهید نتایج تمام درخواست‌ها را بدانید، حتی اگر برخی از آن‌ها ناموفق بوده باشند.
  • Promise.race(iterable): آرایه‌ای از Promiseها را می‌گیرد و Promise را برمی‌گرداند که به محض اینکه یکی از Promiseهای موجود در آرایه resolve یا reject شود، resolve یا reject می‌شود.

async function fetchMultipleData() {
    const userUrl = 'https://api.example.com/users/1';
    const postsUrl = 'https://api.example.com/users/1/posts';
    const commentsUrl = 'https://api.example.com/comments?postId=1';

    try {
        const [userData, postsData, commentsData] = await Promise.all([
            fetch(userUrl).then(res => {
                if (!res.ok) throw new Error(`User data fetch failed: ${res.status}`);
                return res.json();
            }),
            fetch(postsUrl).then(res => {
                if (!res.ok) throw new Error(`Posts data fetch failed: ${res.status}`);
                return res.json();
            }),
            fetch(commentsUrl).then(res => {
                if (!res.ok) throw new Error(`Comments data fetch failed: ${res.status}`);
                return res.json();
            })
        ]);

        console.log('User Data:', userData);
        console.log('Posts Data:', postsData);
        console.log('Comments Data:', commentsData);
    } catch (error) {
        console.error('One of the concurrent fetches failed:', error);
    }

    // مثال با Promise.allSettled
    try {
        const results = await Promise.allSettled([
            fetch('https://api.example.com/valid-endpoint').then(res => res.json()),
            fetch('https://api.example.com/invalid-endpoint').then(res => res.json()), // این احتمالا خطا می‌دهد
            Promise.resolve('Some resolved value')
        ]);

        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index} fulfilled with value:`, result.value);
            } else {
                console.warn(`Promise ${index} rejected with reason:`, result.reason);
            }
        });
    } catch (error) {
        // این بلاک تنها زمانی اجرا می‌شود که Promise.allSettled خودش خطایی داشته باشد، نه Promiseهای داخلی
        console.error("This catch block won't typically be hit for individual promise rejections with allSettled.");
    }
}

// fetchMultipleData();

نتیجه‌گیری

همانطور که دیدیم، تعامل با APIها ستون فقرات برنامه‌های وب مدرن است. جاوا اسکریپت دو ابزار اصلی برای این کار ارائه می‌دهد: XMLHttpRequest (AJAX) و Fetch API. در حالی که XHR نقش پیشگامانه خود را در فعال کردن ارتباطات ناهمزمان ایفا کرد و هنوز هم در کدهای legacy یا در کتابخانه‌هایی مانند Axios برای سازگاری با مرورگرهای قدیمی استفاده می‌شود، Fetch API با اینترفیس مبتنی بر Promise و سینتکس تمیزتر خود، انتخاب ارجح برای توسعه وب مدرن است.

با درک عمیق از پروتکل HTTP، مدیریت موثر خطاها، به‌کارگیری الگوهای پیشرفته مانند async/await، و استفاده از ابزارهای کمکی مانند Axios، توسعه‌دهندگان می‌توانند برنامه‌های وب کارآمد، قابل اعتماد و کاربرپسندی بسازند که به طور مؤثر با منابع داده ارتباط برقرار کنند.

تسلط بر این مفاهیم و تکنیک‌ها، نه تنها به شما کمک می‌کند تا کدهای بهتری بنویسید، بلکه توانایی شما را در اشکال‌زدایی مشکلات شبکه و ساخت برنامه‌های مقیاس‌پذیر و مقاوم در برابر خطا نیز افزایش می‌دهد. همواره بهترین روش‌ها را رعایت کنید و از ابزارهای مدرن برای ساده‌سازی فرآیندهای توسعه خود بهره ببرید.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان