node-fetch

Node.js TypeScript環境向けのFetch APIポリフィル。TypeScript型定義付きでブラウザのFetch APIと同じインターフェースを提供。コードの共有とポータビリティを実現し、フルスタックTypeScript開発で一貫した開発体験を提供。

HTTPクライアントNode.js軽量Fetch APIPromiseESM

GitHub概要

node-fetch/node-fetch

A light-weight module that brings the Fetch API to Node.js

スター8,841
ウォッチ89
フォーク1,047
作成日:2015年1月26日
言語:JavaScript
ライセンス:MIT License

トピックス

fetchfetch-apihacktoberfesthttphttp-clientnode-fetchpromisespec-compliantstreamwhatwgwhatwg-fetch

スター履歴

node-fetch/node-fetch Star History
データ取得日時: 2025/7/18 06:02

ライブラリ

node-fetch

概要

node-fetchは「Node.js環境にFetch APIを提供する軽量モジュール」として開発された、Node.jsでブラウザ標準のFetch APIを利用可能にするライブラリです。ブラウザ環境と同じAPIで一貫したHTTP通信を実現し、PromiseベースのシンプルなAPI、ストリーミングサポート、軽量設計を提供。2025年現在、Node.js 18+ではネイティブのFetch APIが利用可能になったものの、Node.js環境での長年の実績と豊富なカスタマイズオプションにより、依然として多くのプロジェクトで利用されています。

詳細

node-fetch 2025年版はNode.jsのネイティブFetch API対応により大きな転換期を迎えていますが、従来のプロジェクトでの安定性、カスタムエージェント対応、Node.js Streamとの統合、高度な設定オプションを活かして特定の用途で価値を提供し続けています。ESM対応、TypeScript型定義、AbortController対応、FormData処理などモダンなWeb標準に準拠。ブラウザとサーバーで統一されたAPIにより、フルスタック開発でのコード共有と保守性向上に貢献するライブラリとして位置づけられています。

主な特徴

  • Fetch API準拠: ブラウザ標準のFetch APIと同一インターフェース
  • ストリーミング対応: Node.js Readableストリームによる効率的なデータ処理
  • 軽量設計: 必要最小限の依存関係による軽量な実装
  • カスタムエージェント: HTTP/HTTPSエージェントによる詳細なネットワーク制御
  • ESM/CommonJS対応: モジュールシステムの柔軟な対応
  • TypeScript対応: 完全な型定義による開発体験向上

メリット・デメリット

メリット

  • ブラウザとNode.jsで統一されたFetch API体験
  • 軽量で必要最小限の依存関係
  • Node.js Streamとの優れた統合
  • カスタムHTTPエージェントによる詳細なネットワーク制御
  • 豊富な実績と安定性による信頼性
  • TypeScript完全対応による開発効率向上

デメリット

  • Node.js 18+ではネイティブFetch APIが利用可能で必要性が低下
  • 最新のHTTP/2機能サポートが限定的
  • 他の高機能HTTPクライアントと比較して機能が基本的
  • メンテナンス頻度が減少傾向
  • ブラウザ環境では利用不可(Node.js専用)
  • 複雑な認証処理では他ライブラリの方が適している場合あり

参考ページ

書き方の例

インストールと基本セットアップ

# node-fetch最新版のインストール(v3.x)
npm install node-fetch

# v2系(CommonJS対応)のインストール
npm install node-fetch@2

# TypeScript型定義(v3では内蔵)
npm install @types/node-fetch --save-dev  # v2用

# Node.jsバージョン確認(v12.20.0以降推奨)
node --version
// ES6 Modules(v3推奨)
import fetch from 'node-fetch';

// CommonJS(v2または動的import)
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));

// v2系のCommonJS
const fetch = require('node-fetch');

// グローバルfetchポリフィル(ESM)
import fetch, {
  Blob,
  blobFrom,
  blobFromSync,
  File,
  fileFrom,
  fileFromSync,
  FormData,
  Headers,
  Request,
  Response,
} from 'node-fetch'

if (!globalThis.fetch) {
  globalThis.fetch = fetch
  globalThis.Headers = Headers
  globalThis.Request = Request
  globalThis.Response = Response
}

基本的なリクエスト(GET/POST/PUT/DELETE)

import fetch from 'node-fetch';

// 基本的なGETリクエスト
async function basicGetRequest() {
  try {
    const response = await fetch('https://api.example.com/users', {
      headers: {
        'Accept': 'application/json',
        'User-Agent': 'MyApp/1.0 (node-fetch)'
      }
    });

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

    const data = await response.json();
    console.log('ユーザーデータ:', data);
    return data;

  } catch (error) {
    console.error('GETリクエストエラー:', error.message);
    throw error;
  }
}

// プレーンテキスト/HTML取得
async function fetchText() {
  try {
    const response = await fetch('https://example.com/');
    const body = await response.text();
    
    console.log('HTMLコンテンツ:', body);
    return body;

  } catch (error) {
    console.error('テキスト取得エラー:', error.message);
  }
}

// JSONデータ取得
async function fetchJsonData() {
  try {
    const response = await fetch('https://api.github.com/users/github');
    const data = await response.json();
    
    console.log('GitHubユーザー情報:', data);
    return data;

  } catch (error) {
    console.error('JSON取得エラー:', error.message);
  }
}

// POSTリクエスト(シンプル)
async function simplePostRequest() {
  try {
    const response = await fetch('https://httpbin.org/post', {
      method: 'POST',
      body: 'a=1&b=2',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    const data = await response.json();
    console.log('POSTレスポンス:', data);
    return data;

  } catch (error) {
    console.error('POSTエラー:', error.message);
  }
}

// POSTリクエスト(JSON送信)
async function postJsonRequest() {
  try {
    const requestData = {
      name: '田中太郎',
      email: '[email protected]',
      age: 30,
      department: 'エンジニアリング'
    };

    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      body: JSON.stringify(requestData),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer your-jwt-token'
      }
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`作成失敗: ${response.status} - ${errorText}`);
    }

    const newUser = await response.json();
    console.log('新規ユーザー作成:', newUser);
    return newUser;

  } catch (error) {
    console.error('ユーザー作成エラー:', error.message);
    throw error;
  }
}

// PUTリクエスト(更新)
async function putRequest() {
  try {
    const updateData = {
      name: '田中二郎',
      email: '[email protected]',
      department: 'プロダクト開発'
    };

    const response = await fetch('https://api.example.com/users/123', {
      method: 'PUT',
      body: JSON.stringify(updateData),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer your-jwt-token'
      }
    });

    if (response.status === 200) {
      const updatedUser = await response.json();
      console.log('ユーザー更新成功:', updatedUser);
      return updatedUser;
    } else {
      throw new Error(`更新失敗: ${response.status}`);
    }

  } catch (error) {
    console.error('更新エラー:', error.message);
    throw error;
  }
}

// DELETEリクエスト
async function deleteRequest() {
  try {
    const response = await fetch('https://api.example.com/users/123', {
      method: 'DELETE',
      headers: {
        'Authorization': 'Bearer your-jwt-token'
      }
    });

    if (response.status === 204) {
      console.log('ユーザー削除成功');
      return true;
    } else {
      throw new Error(`削除失敗: ${response.status}`);
    }

  } catch (error) {
    console.error('削除エラー:', error.message);
    return false;
  }
}

// URLSearchParamsを使用したフォーム送信
async function postFormData() {
  try {
    const params = new URLSearchParams();
    params.append('username', 'testuser');
    params.append('password', 'secret123');
    params.append('remember', 'true');

    const response = await fetch('https://api.example.com/login', {
      method: 'POST',
      body: params
    });

    const result = await response.json();
    console.log('ログイン結果:', result);
    return result;

  } catch (error) {
    console.error('フォーム送信エラー:', error.message);
  }
}

// レスポンスメタデータの取得
async function responseMetadata() {
  try {
    const response = await fetch('https://api.example.com/status');

    console.log('成功:', response.ok);
    console.log('ステータスコード:', response.status);
    console.log('ステータステキスト:', response.statusText);
    console.log('全ヘッダー:', response.headers.raw());
    console.log('Content-Type:', response.headers.get('content-type'));

    if (response.ok) {
      const data = await response.json();
      return data;
    }

  } catch (error) {
    console.error('メタデータ取得エラー:', error.message);
  }
}

// 使用例
basicGetRequest();
fetchJsonData();
postJsonRequest();

高度な設定とカスタマイズ(ヘッダー、認証、タイムアウト等)

import fetch from 'node-fetch';
import http from 'node:http';
import https from 'node:https';

// カスタムエージェントの設定
const httpAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 10,
  timeout: 10000
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 10,
  timeout: 10000,
  rejectUnauthorized: true
});

// 高度なヘッダー設定と認証
async function advancedHeaders() {
  try {
    const response = await fetch('https://api.example.com/protected', {
      method: 'GET',
      headers: {
        'Authorization': 'Bearer your-jwt-token',
        'Accept': 'application/json',
        'User-Agent': 'MyApp/1.0 (node-fetch)',
        'X-API-Version': 'v2',
        'X-Request-ID': generateRequestId(),
        'Accept-Language': 'ja-JP,en-US;q=0.9',
        'Cache-Control': 'no-cache'
      },
      timeout: 10000,
      // カスタムエージェント関数
      agent: function(parsedURL) {
        if (parsedURL.protocol === 'http:') {
          return httpAgent;
        } else {
          return httpsAgent;
        }
      }
    });

    console.log('保護されたリソースにアクセス成功');
    return await response.json();

  } catch (error) {
    console.error('認証エラー:', error.message);
    throw error;
  }
}

// デフォルトオプションの設定
const defaultOptions = {
  method: 'GET',
  headers: {
    'User-Agent': 'MyApp/1.0 (node-fetch)',
    'Accept': 'application/json'
  },
  redirect: 'follow',      // リダイレクト自動追従
  follow: 20,              // 最大リダイレクト回数
  compress: true,          // gzip/deflate圧縮サポート
  size: 0,                 // レスポンスボディの最大サイズ(0=無制限)
  agent: null,             // HTTPエージェント
  highWaterMark: 16384,    // ストリーム内部バッファサイズ
  insecureHTTPParser: false // 不正なHTTPヘッダーの許可
};

// SSL/TLS設定
async function sslConfiguration() {
  try {
    const response = await fetch('https://secure-api.example.com/data', {
      agent: new https.Agent({
        rejectUnauthorized: true,  // SSL証明書検証有効
        ca: [/* CA証明書 */],      // CA証明書設定
        cert: '/* クライアント証明書 */',
        key: '/* 秘密鍵 */',
        passphrase: 'certificate-passphrase'
      })
    });

    console.log('SSL通信成功');
    return await response.json();

  } catch (error) {
    console.error('SSL設定エラー:', error.message);
  }
}

// プロキシ設定
async function proxyConfiguration() {
  try {
    const ProxyAgent = await import('proxy-agent');
    
    const response = await fetch('https://api.example.com/data', {
      agent: new ProxyAgent.ProxyAgent('http://proxy.example.com:8080'),
      headers: {
        'User-Agent': 'MyApp via Proxy'
      }
    });

    console.log('プロキシ経由でレスポンス取得');
    return await response.json();

  } catch (error) {
    console.error('プロキシエラー:', error.message);
  }
}

// Cookieハンドリング
async function cookieHandling() {
  try {
    // Cookieを送信
    const response = await fetch('https://api.example.com/session-data', {
      headers: {
        'Cookie': 'session_id=abc123; user_pref=dark_mode'
      }
    });

    // レスポンスからCookie取得
    const setCookieHeader = response.headers.get('set-cookie');
    if (setCookieHeader) {
      console.log('受信Cookie:', setCookieHeader);
    }

    // 複数のSet-Cookieヘッダーを取得
    const rawHeaders = response.headers.raw();
    const setCookies = rawHeaders['set-cookie'];
    if (setCookies) {
      console.log('全Cookieヘッダー:', setCookies);
    }

    return await response.json();

  } catch (error) {
    console.error('Cookie処理エラー:', error.message);
  }
}

// リダイレクト制御
async function redirectControl() {
  try {
    // リダイレクトを手動で処理
    const response = await fetch('https://httpbin.org/status/301', {
      redirect: 'manual'
    });

    if (response.status === 301 || response.status === 302) {
      const locationURL = new URL(response.headers.get('location'), response.url);
      console.log('リダイレクト先:', locationURL.href);
      
      // 手動でリダイレクト先にリクエスト
      const response2 = await fetch(locationURL, { redirect: 'manual' });
      console.log('リダイレクト後のステータス:', response2.status);
      return await response2.json();
    }

  } catch (error) {
    console.error('リダイレクト処理エラー:', error.message);
  }
}

// ストリーミング設定
async function streamingConfiguration() {
  try {
    const response = await fetch('https://api.example.com/large-dataset', {
      highWaterMark: 1024 * 1024, // 1MBバッファ
      compress: true
    });

    if (!response.ok) {
      throw new Error(`ストリーミングエラー: ${response.statusText}`);
    }

    // レスポンスのクローン作成(並行処理用)
    const clonedResponse = response.clone();

    // 並行でレスポンス処理
    const [jsonResult, textResult] = await Promise.all([
      response.json(),
      clonedResponse.text()
    ]);

    console.log('JSON結果:', jsonResult);
    console.log('テキスト長:', textResult.length);

  } catch (error) {
    console.error('ストリーミング設定エラー:', error.message);
  }
}

function generateRequestId() {
  return 'req-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}

// 使用例
advancedHeaders();
sslConfiguration();
cookieHandling();

エラーハンドリングとリトライ機能

import fetch, { AbortError } from 'node-fetch';

// カスタムエラークラス
class HTTPResponseError extends Error {
  constructor(response) {
    super(`HTTP Error Response: ${response.status} ${response.statusText}`);
    this.response = response;
  }
}

// ステータスチェック関数
const checkStatus = response => {
  if (response.ok) {
    return response;
  } else {
    throw new HTTPResponseError(response);
  }
};

// 包括的なエラーハンドリング
async function comprehensiveErrorHandling() {
  try {
    const response = await fetch('https://api.example.com/users', {
      timeout: 10000,
      headers: {
        'Authorization': 'Bearer your-jwt-token'
      }
    });

    checkStatus(response);
    return await response.json();

  } catch (error) {
    if (error instanceof HTTPResponseError) {
      const status = error.response.status;
      const errorBody = await error.response.text();

      console.error(`HTTP Error: ${status}`);

      switch (status) {
        case 400:
          console.error('Bad Request: リクエストパラメータを確認してください');
          break;
        case 401:
          console.error('Unauthorized: 認証情報が無効です');
          // トークンリフレッシュの実行
          return await refreshTokenAndRetry();
        case 403:
          console.error('Forbidden: アクセス権限がありません');
          break;
        case 404:
          console.error('Not Found: リソースが存在しません');
          break;
        case 429:
          console.error('Rate Limited: 後でもう一度お試しください');
          const retryAfter = error.response.headers.get('retry-after');
          if (retryAfter) {
            console.error(`リトライ可能時刻: ${retryAfter}秒後`);
          }
          break;
        case 500:
          console.error('Internal Server Error: サーバー側の問題');
          break;
        case 502:
          console.error('Bad Gateway: サーバーが一時的に利用不可');
          break;
        case 503:
          console.error('Service Unavailable: サーバーが過負荷状態');
          break;
        default:
          console.error('エラー詳細:', errorBody);
      }

    } else if (error instanceof AbortError) {
      console.error('リクエストがキャンセルされました');
    } else {
      console.error('ネットワークエラー:', error.message);
    }

    throw error;
  }
}

// マニュアルリトライ戦略
async function manualRetryStrategy() {
  const maxRetries = 3;
  const baseDelay = 1000; // 1秒

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.example.com/unstable-endpoint', {
        timeout: 10000
      });

      checkStatus(response);
      return await response.json();

    } catch (error) {
      const isLastAttempt = attempt === maxRetries;

      if (isLastAttempt) {
        console.error(`最大リトライ回数に達しました: ${error.message}`);
        throw error;
      }

      // リトライすべきエラーかチェック
      const shouldRetry = shouldRetryError(error);

      if (!shouldRetry) {
        console.error(`リトライ不可エラー: ${error.message}`);
        throw error;
      }

      // 指数バックオフ
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`リトライ ${attempt + 1}/${maxRetries} - ${delay}ms後に再実行`);

      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// リトライ判定関数
function shouldRetryError(error) {
  if (error instanceof AbortError) {
    return false; // タイムアウトは通常リトライしない
  }

  if (error instanceof HTTPResponseError) {
    const status = error.response.status;
    return [408, 429, 500, 502, 503, 504].includes(status);
  }

  // ネットワークエラーはリトライ
  return true;
}

// プログレッシブ障害回復(フェイルオーバー)
async function progressiveRecovery() {
  const endpoints = [
    'https://primary-api.example.com/data',
    'https://backup-api.example.com/data',
    'https://fallback-api.example.com/data'
  ];

  for (let i = 0; i < endpoints.length; i++) {
    try {
      console.log(`試行 ${i + 1}: ${endpoints[i]}`);

      const response = await fetch(endpoints[i], {
        timeout: 5000 + (i * 2000) // 段階的にタイムアウト延長
      });

      checkStatus(response);
      console.log(`エンドポイント ${i + 1} で成功`);
      return await response.json();

    } catch (error) {
      console.log(`エンドポイント ${i + 1} 失敗: ${error.message}`);

      if (i === endpoints.length - 1) {
        console.error('全エンドポイントが失敗しました');
        throw new Error('全エンドポイントが失敗しました');
      }
    }
  }
}

// 部分的失敗許容(並行リクエスト)
async function partialFailureTolerance() {
  const endpoints = [
    'https://api.example.com/reliable',
    'https://api.example.com/unreliable',
    'https://api.example.com/another'
  ];

  const requests = endpoints.map(url =>
    fetch(url, { timeout: 3000 })
      .then(response => ({ success: true, url, data: response.json() }))
      .catch(error => ({ success: false, url, error: error.message }))
  );

  const results = await Promise.all(requests);

  const successful = results.filter(result => result.success);
  const failed = results.filter(result => !result.success);

  console.log(`成功: ${successful.length}, 失敗: ${failed.length}`);
  console.log('失敗詳細:', failed);

  return { successful, failed };
}

// ネットワーク・運用例外のハンドリング
async function handleNetworkExceptions() {
  try {
    await fetch('https://domain.invalid/');
  } catch (error) {
    console.log('ネットワークエラーキャッチ:', error.message);
    
    if (error.code === 'ENOTFOUND') {
      console.error('DNS解決失敗: ドメインが存在しません');
    } else if (error.code === 'ECONNREFUSED') {
      console.error('接続拒否: サーバーがポートで待機していません');
    } else if (error.code === 'ECONNRESET') {
      console.error('接続リセット: サーバーが接続を切断しました');
    } else {
      console.error('その他のネットワークエラー:', error.code);
    }
  }
}

// トークンリフレッシュ機能
async function refreshTokenAndRetry() {
  try {
    const refreshResponse = await fetch('https://api.example.com/auth/refresh', {
      method: 'POST',
      body: JSON.stringify({ refresh_token: 'stored-refresh-token' }),
      headers: { 'Content-Type': 'application/json' }
    });

    checkStatus(refreshResponse);
    const tokens = await refreshResponse.json();
    console.log('トークンリフレッシュ成功');

    // 新しいトークンで元のリクエストを再実行
    return await fetch('https://api.example.com/users', {
      headers: {
        'Authorization': `Bearer ${tokens.access_token}`
      }
    });

  } catch (error) {
    console.error('トークンリフレッシュ失敗:', error.message);
    throw error;
  }
}

// 使用例
comprehensiveErrorHandling();
manualRetryStrategy();
progressiveRecovery();

並行処理と非同期リクエスト

import fetch from 'node-fetch';
import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';

const streamPipeline = promisify(pipeline);

// 基本的な並行リクエスト
async function basicParallelRequests() {
  const startTime = Date.now();

  try {
    const [users, posts, comments, categories] = await Promise.all([
      fetch('https://api.example.com/users'),
      fetch('https://api.example.com/posts'),
      fetch('https://api.example.com/comments'),
      fetch('https://api.example.com/categories')
    ]);

    const endTime = Date.now();
    console.log(`並行実行時間: ${endTime - startTime}ms`);

    // 並行してJSONパース
    const [usersData, postsData, commentsData, categoriesData] = await Promise.all([
      users.json(),
      posts.json(),
      comments.json(),
      categories.json()
    ]);

    return {
      users: usersData,
      posts: postsData,
      comments: commentsData,
      categories: categoriesData
    };

  } catch (error) {
    console.error('並行リクエストエラー:', error.message);
    throw error;
  }
}

// 部分失敗許容 Promise.allSettled使用
async function parallelWithPartialFailure() {
  const endpoints = [
    'https://api.example.com/endpoint1',
    'https://api.example.com/endpoint2',
    'https://api.example.com/endpoint3',
    'https://api.example.com/unreliable-endpoint'
  ];

  const requests = endpoints.map(url => fetch(url, { timeout: 5000 }));
  const results = await Promise.allSettled(requests);

  const successful = [];
  const failed = [];

  for (let i = 0; i < results.length; i++) {
    const result = results[i];
    if (result.status === 'fulfilled') {
      try {
        const data = await result.value.json();
        successful.push({
          url: endpoints[i],
          data: data
        });
      } catch (parseError) {
        failed.push({
          url: endpoints[i],
          error: 'JSON解析エラー'
        });
      }
    } else {
      failed.push({
        url: endpoints[i],
        error: result.reason.message
      });
    }
  }

  console.log(`成功: ${successful.length}, 失敗: ${failed.length}`);
  return { successful, failed };
}

// 順次データ取得(ページネーション)
async function fetchAllPages() {
  const allData = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    try {
      const response = await fetch(`https://api.example.com/paginated-data?page=${page}&limit=20`, {
        timeout: 10000
      });

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

      const pageData = await response.json();
      allData.push(...pageData.items);

      hasMore = pageData.has_more;
      page++;

      console.log(`ページ ${page - 1} 完了: ${pageData.items.length} 件`);

      // API負荷軽減のための待機
      if (hasMore) {
        await new Promise(resolve => setTimeout(resolve, 200));
      }

    } catch (error) {
      console.error(`ページ ${page} 取得エラー: ${error.message}`);
      break;
    }
  }

  console.log(`合計 ${allData.length} 件のデータを取得`);
  return allData;
}

// 並行ページネーション(高速データ取得)
async function parallelPagination() {
  try {
    // 最初のページから総ページ数を取得
    const firstPageResponse = await fetch('https://api.example.com/paginated-data?page=1&limit=20');
    const firstPageData = await firstPageResponse.json();
    const totalPages = firstPageData.total_pages;
    const allData = [...firstPageData.items];

    if (totalPages > 1) {
      // 残りのページを並行で取得
      const pagePromises = [];
      for (let page = 2; page <= totalPages; page++) {
        pagePromises.push(
          fetch(`https://api.example.com/paginated-data?page=${page}&limit=20`, {
            timeout: 10000
          })
        );
      }

      const pageResults = await Promise.allSettled(pagePromises);

      for (let i = 0; i < pageResults.length; i++) {
        const result = pageResults[i];
        if (result.status === 'fulfilled') {
          try {
            const pageData = await result.value.json();
            allData.push(...pageData.items);
          } catch (parseError) {
            console.error(`ページ ${i + 2} のJSON解析エラー`);
          }
        } else {
          console.error(`ページ ${i + 2} 失敗: ${result.reason.message}`);
        }
      }
    }

    console.log(`並行取得完了: ${allData.length} 件`);
    return allData;

  } catch (error) {
    console.error('並行ページネーションエラー:', error.message);
    return [];
  }
}

// レート制限付き順次実行
async function rateLimitedRequests(urls, requestsPerSecond = 5) {
  const interval = 1000 / requestsPerSecond; // リクエスト間隔(ミリ秒)
  const results = [];

  for (let i = 0; i < urls.length; i++) {
    const startTime = Date.now();

    try {
      const response = await fetch(urls[i], { timeout: 5000 });
      const data = await response.json();

      results.push({
        url: urls[i],
        success: true,
        data: data
      });

      console.log(`${i + 1}/${urls.length} 完了: ${urls[i]}`);

    } catch (error) {
      results.push({
        url: urls[i],
        success: false,
        error: error.message
      });

      console.error(`${i + 1}/${urls.length} 失敗: ${urls[i]}`);
    }

    // レート制限待機
    if (i < urls.length - 1) {
      const elapsed = Date.now() - startTime;
      const waitTime = Math.max(0, interval - elapsed);

      if (waitTime > 0) {
        await new Promise(resolve => setTimeout(resolve, waitTime));
      }
    }
  }

  const successCount = results.filter(r => r.success).length;
  console.log(`処理完了: ${successCount}/${urls.length} 成功`);

  return results;
}

// 同時実行数制限付き並行処理
async function concurrencyLimitedRequests(urls, maxConcurrency = 3) {
  const results = [];
  const executing = [];

  for (const url of urls) {
    const promise = fetch(url, { timeout: 5000 })
      .then(response => response.json())
      .then(data => ({ url, success: true, data }))
      .catch(error => ({ url, success: false, error: error.message }));

    results.push(promise);

    if (urls.length >= maxConcurrency) {
      executing.push(promise);

      if (executing.length >= maxConcurrency) {
        await Promise.race(executing);
        executing.splice(executing.findIndex(p => p === promise), 1);
      }
    }
  }

  const finalResults = await Promise.all(results);
  const successCount = finalResults.filter(r => r.success).length;

  console.log(`同時実行数制限処理完了: ${successCount}/${urls.length} 成功`);
  return finalResults;
}

// ストリーミングデータ処理
async function streamingProcessing() {
  try {
    const response = await fetch('https://api.example.com/stream/data', {
      timeout: 60000
    });

    if (!response.ok) {
      throw new Error(`ストリーミングエラー: ${response.statusText}`);
    }

    // Node.js 14+ での非同期イテレータ使用
    let processedChunks = 0;
    for await (const chunk of response.body) {
      try {
        const data = JSON.parse(chunk.toString());
        console.log('処理データ:', data);
        processedChunks++;

        if (processedChunks % 100 === 0) {
          console.log(`${processedChunks} チャンク処理完了`);
        }
      } catch (parseError) {
        console.error('チャンク解析エラー:', parseError.message);
      }
    }

    console.log(`ストリーミング処理完了: ${processedChunks} チャンク`);

  } catch (error) {
    console.error('ストリーミングエラー:', error.message);
  }
}

// ファイルダウンロード(ストリーミング)
async function downloadFile() {
  try {
    const response = await fetch('https://example.com/large-file.zip');

    if (!response.ok) {
      throw new Error(`ダウンロードエラー: ${response.statusText}`);
    }

    await streamPipeline(response.body, createWriteStream('./downloaded-file.zip'));
    console.log('ファイルダウンロード完了');

  } catch (error) {
    console.error('ファイルダウンロードエラー:', error.message);
  }
}

// 使用例
const sampleUrls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3'
];

basicParallelRequests();
parallelWithPartialFailure();
fetchAllPages();
rateLimitedRequests(sampleUrls, 2);

フレームワーク統合と実用例

// Express.js統合例
import express from 'express';
import fetch from 'node-fetch';

const app = express();
app.use(express.json());

// APIプロキシエンドポイント
app.get('/api/proxy/:service/*', async (req, res) => {
  try {
    const { service } = req.params;
    const path = req.params[0];
    const serviceUrls = {
      'users': 'https://users-api.example.com',
      'posts': 'https://posts-api.example.com',
      'comments': 'https://comments-api.example.com'
    };

    const baseUrl = serviceUrls[service];
    if (!baseUrl) {
      return res.status(404).json({ error: 'サービスが見つかりません' });
    }

    const queryString = new URLSearchParams(req.query).toString();
    const targetUrl = `${baseUrl}/${path}${queryString ? '?' + queryString : ''}`;

    const response = await fetch(targetUrl, {
      method: req.method,
      headers: {
        'Authorization': req.headers.authorization,
        'Content-Type': 'application/json'
      },
      body: req.method !== 'GET' ? JSON.stringify(req.body) : null,
      timeout: 10000
    });

    if (!response.ok) {
      return res.status(response.status).json({
        error: 'プロキシリクエストが失敗しました',
        status: response.status
      });
    }

    const data = await response.json();
    res.json(data);

  } catch (error) {
    console.error('プロキシエラー:', error.message);
    res.status(500).json({
      error: 'プロキシリクエストが失敗しました',
      message: error.message
    });
  }
});

// APIクライアントサービスクラス
class ApiService {
  constructor(baseURL, apiKey) {
    this.baseURL = baseURL;
    this.apiKey = apiKey;
  }

  // 共通リクエスト設定
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const defaultOptions = {
      timeout: 10000,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        'User-Agent': 'Node.js App/1.0'
      }
    };

    const mergedOptions = {
      ...defaultOptions,
      ...options,
      headers: {
        ...defaultOptions.headers,
        ...options.headers
      }
    };

    return await fetch(url, mergedOptions);
  }

  // ユーザー一覧取得
  async getUsers(page = 1, limit = 20) {
    try {
      const response = await this.request(`/users?page=${page}&limit=${limit}`);

      if (!response.ok) {
        throw new Error(`ユーザー取得失敗: ${response.status}`);
      }

      const data = await response.json();
      return {
        data: data,
        pagination: {
          page,
          limit,
          total: parseInt(response.headers.get('x-total-count') || '0')
        }
      };

    } catch (error) {
      console.error('ユーザー取得エラー:', error.message);
      throw error;
    }
  }

  // ユーザー作成
  async createUser(userData) {
    try {
      const response = await this.request('/users', {
        method: 'POST',
        body: JSON.stringify(userData)
      });

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`ユーザー作成失敗: ${response.status} - ${errorText}`);
      }

      return await response.json();

    } catch (error) {
      console.error('ユーザー作成エラー:', error.message);
      throw error;
    }
  }

  // ユーザー更新
  async updateUser(id, userData) {
    try {
      const response = await this.request(`/users/${id}`, {
        method: 'PUT',
        body: JSON.stringify(userData)
      });

      if (!response.ok) {
        throw new Error(`ユーザー更新失敗: ${response.status}`);
      }

      return await response.json();

    } catch (error) {
      console.error('ユーザー更新エラー:', error.message);
      throw error;
    }
  }

  // ユーザー削除
  async deleteUser(id) {
    try {
      const response = await this.request(`/users/${id}`, {
        method: 'DELETE'
      });

      return response.status === 204;

    } catch (error) {
      console.error('ユーザー削除エラー:', error.message);
      return false;
    }
  }
}

// FormDataとファイルアップロード
import { FormData, File, fileFromSync } from 'node-fetch';

async function uploadFile() {
  try {
    const formData = new FormData();
    const httpbin = 'https://httpbin.org/post';

    // テキストフィールド追加
    formData.set('description', 'ファイルアップロードテスト');
    formData.set('category', 'documents');

    // ファイル追加
    const fileContent = new Uint8Array([97, 98, 99]); // "abc"
    const file = new File([fileContent], 'test.txt', { type: 'text/plain' });
    formData.set('file-upload', file, 'uploaded-file.txt');

    // ファイルシステムからファイル読み込み
    // const fileFromDisk = fileFromSync('./input.txt', 'text/plain');
    // formData.set('disk-file', fileFromDisk);

    const response = await fetch(httpbin, {
      method: 'POST',
      body: formData
    });

    const result = await response.json();
    console.log('アップロード結果:', result);

  } catch (error) {
    console.error('ファイルアップロードエラー:', error.message);
  }
}

// カスタムBlob/Fileオブジェクト
async function customBlobUpload() {
  try {
    const formData = new FormData();

    // カスタムオブジェクト(最小要件を満たす)
    const customBlob = {
      [Symbol.toStringTag]: 'Blob',
      size: 3,
      *stream() {
        yield new Uint8Array([97, 98, 99]);
      },
      arrayBuffer() {
        return new Uint8Array([97, 98, 99]).buffer;
      }
    };

    formData.append('custom-upload', customBlob, 'custom.txt');

    const response = await fetch('https://httpbin.org/post', {
      method: 'POST',
      body: formData
    });

    const result = await response.json();
    console.log('カスタムアップロード結果:', result);

  } catch (error) {
    console.error('カスタムアップロードエラー:', error.message);
  }
}

// リクエスト中断機能
async function abortableRequest() {
  // AbortController(Node.js 14.17.0+でグローバル)
  const AbortController = globalThis.AbortController || (await import('abort-controller')).default;
  const controller = new AbortController();

  // 5秒後に中断
  const timeout = setTimeout(() => {
    controller.abort();
  }, 5000);

  try {
    const response = await fetch('https://httpbin.org/delay/10', {
      signal: controller.signal
    });

    clearTimeout(timeout);
    const data = await response.json();
    console.log('リクエスト成功:', data);

  } catch (error) {
    clearTimeout(timeout);
    if (error.name === 'AbortError') {
      console.log('リクエストが中断されました');
    } else {
      console.error('リクエストエラー:', error.message);
    }
  }
}

// Webhook受信処理(Express.js)
import { Response } from 'node-fetch';

app.post('/webhook', async (req, res) => {
  try {
    // node-fetchのResponseクラスでリクエストボディを解析
    const formData = await new Response(req, {
      headers: req.headers
    }).formData();

    const allFields = [...formData];
    console.log('受信フィールド:', allFields);

    const uploadedFile = formData.get('uploaded-file');
    if (uploadedFile) {
      const fileBuffer = await uploadedFile.arrayBuffer();
      const fileText = await uploadedFile.text();
      console.log('ファイル内容:', fileText);
    }

    res.json({ status: 'webhook受信成功' });

  } catch (error) {
    console.error('Webhook処理エラー:', error.message);
    res.status(500).json({ error: 'Webhook処理失敗' });
  }
});

// 使用例
const apiService = new ApiService('https://api.example.com', 'your-api-key');

// アプリケーション実行例
async function runApplication() {
  try {
    const users = await apiService.getUsers(1, 10);
    console.log('取得ユーザー:', users);

    const newUser = await apiService.createUser({
      name: '新規ユーザー',
      email: '[email protected]'
    });
    console.log('作成ユーザー:', newUser);

    await uploadFile();
    await abortableRequest();

  } catch (error) {
    console.error('アプリケーションエラー:', error.message);
  }
}

// Express.js サーバー起動
app.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました');
});

// Node.js アプリケーション実行
runApplication();