عميل HTTP بلغة TypeScript: الطلبات الصحيحة ومعالجة الردود

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا عميل HTTP الصحيح هو أساس تطبيق الويب الحديث

أي تطبيق ويب حديث يتفاعل مع الخادم عبر HTTP. سواء كان تحميل قائمة المنتجات، أو إرسال نموذج تسجيل، أو الحصول على بيانات للوحة المعلومات - كل هذا يتلخص في إرسال طلبات HTTP. باستخدام TypeScript، مع نظام الأنواع القوي الخاص به، يمكنك بناء عميل HTTP ليس فقط عملياً، بل موثوقاً وقابلاً للتنبؤ حقاً.

في هذه المقالة، سنشرح كيفية إنشاء عميل HTTP باستخدام TypeScript، والذي سيعالج الأخطاء، ويصنف الاستجابات بشكل صحيح، ويدير الرؤوس، ويدعم middleware. ستتعرف على لماذا استخدام fetch الخام بدون غلاف هو ممارسة سيئة، وكيفية تحويله إلى أداة قوية.

الأساسيات: من fetch الخام إلى عميل مصنف

لنبدأ بمثال أساسي. fetch المدمج في JavaScript هو نقطة انطلاق جيدة، لكنه لا يوفر تصنيفاً مناسباً ومعالجة للأخطاء. باستخدام TypeScript، يمكننا تغليفه في دالة خاصة بنا.

غلاف بسيط لـ fetch

لننشئ كلاساً أساسياً HttpClient يستقبل عنوان URL أساسياً وإعدادات.

interface RequestOptions {
  headers?: Record<string, string>;
  params?: Record<string, string>;
}

class HttpClient {
  private baseUrl: string;
  private defaultHeaders: Record<string, string>;

  constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {
    this.baseUrl = baseUrl;
    this.defaultHeaders = defaultHeaders;
  }

  private buildUrl(path: string, params?: Record<string, string>): string {
    const url = new URL(`${this.baseUrl}${path}`);
    if (params) {
      Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value));
    }
    return url.toString();
  }

  async get<T>(path: string, options?: RequestOptions): Promise<T> {
    const url = this.buildUrl(path, options?.params);
    const response = await fetch(url, {
      method: 'GET',
      headers: { ...this.defaultHeaders, ...options?.headers },
    });

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

    return response.json() as Promise<T>;
  }
}

الآن استدعاء client.get<User>('/users/1') سيعيد كائن User مصنفاً. هذا أفضل بكثير من العمل مع any.

معالجة متقدمة للأخطاء وتصنيف الاستجابات

رمي خطأ بسيط ليس أفضل نهج. نحتاج إلى التمييز بين أخطاء الشبكة، وأخطاء الخادم (4xx, 5xx)، وأخطاء تحليل JSON. لننشئ كلاسات أخطاء خاصة بنا.

إنشاء كلاسات الأخطاء

class HttpError extends Error {
  status: number;
  body: any;

  constructor(message: string, status: number, body?: any) {
    super(message);
    this.name = 'HttpError';
    this.status = status;
    this.body = body;
  }
}

class NetworkError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'NetworkError';
  }
}

class ParseError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'ParseError';
  }
}

معالجة محسنة في طريقة get

async get<T>(path: string, options?: RequestOptions): Promise<T> {
  try {
    const url = this.buildUrl(path, options?.params);
    const response = await fetch(url, {
      method: 'GET',
      headers: { ...this.defaultHeaders, ...options?.headers },
    });

    let body: any;
    try {
      body = await response.json();
    } catch {
      throw new ParseError('Failed to parse response as JSON');
    }

    if (!response.ok) {
      throw new HttpError(
        `Request failed with status ${response.status}`,
        response.status,
        body
      );
    }

    return body as T;
  } catch (er                

المدونات

توصيات الكتب