¥
立即购买

JavaScript网络请求生成器

4 浏览
1 试用
0 购买
Dec 8, 2025更新

本提示词专门用于生成符合现代JavaScript最佳实践的fetch请求代码。它能够根据不同的应用场景和需求,生成包含错误处理、数据格式转换、请求配置等完整功能的网络请求代码。支持GET、POST、PUT、DELETE等多种HTTP方法,可自定义请求头、超时设置和认证信息,确保生成的代码具备良好的可读性、可维护性和错误处理能力,适用于前端开发、API调用和数据交互等多种业务场景。

代码实现

/**
 * Modern fetch wrapper for POSTing JSON to ExampleAI text generation API
 * - Bearer token auth
 * - Timeout with AbortController
 * - Exponential backoff retries for transient errors (429/5xx, network)
 * - Robust error normalization and response parsing (JSON/text)
 *
 * Works in modern browsers and Node.js >= 18 (built-in fetch).
 */

/** @typedef {{status:number, statusText:string, url:string, method:string, requestId?:string, headers:Record<string,string>, body?:unknown}} HTTPErrorMeta */

class HTTPError extends Error {
  /**
   * @param {string} message
   * @param {HTTPErrorMeta} meta
   */
  constructor(message, meta) {
    super(message);
    this.name = 'HTTPError';
    this.status = meta.status;
    this.statusText = meta.statusText;
    this.url = meta.url;
    this.method = meta.method;
    this.headers = meta.headers;
    this.requestId = meta.requestId;
    this.body = meta.body;
  }
}

/**
 * Safe JSON parse with fallback to text.
 * @param {Response} res
 * @returns {Promise<{ data: any, rawText?: string }>}
 */
async function parseBody(res) {
  const contentType = res.headers.get('content-type') || '';
  const isJSON = contentType.includes('application/json');
  if (isJSON) {
    try {
      const data = await res.json();
      return { data };
    } catch (e) {
      // Fallthrough to text if JSON malformed
    }
  }
  const rawText = await res.text();
  let data = rawText;
  if (isJSON) {
    // If declared as JSON but failed to parse, preserve text for diagnostics
    data = { parseError: true, raw: rawText };
  }
  return { data, rawText };
}

/**
 * Normalize response headers to a simple object.
 * @param {Headers} headers
 */
function headersToObject(headers) {
  const obj = {};
  headers.forEach((v, k) => {
    obj[k.toLowerCase()] = v;
  });
  return obj;
}

/**
 * Determine if an HTTP status is transient and worth retrying.
 * @param {number} status
 */
function isTransientStatus(status) {
  // 429 Too Many Requests, 408 Request Timeout, 5xx Server Errors
  return status === 429 || status === 408 || (status >= 500 && status < 600);
}

/**
 * Parse Retry-After header to milliseconds if present.
 * @param {Headers} headers
 */
function parseRetryAfterMs(headers) {
  const v = headers.get('retry-after');
  if (!v) return undefined;
  const seconds = Number(v);
  if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);
  const date = new Date(v).getTime();
  if (!Number.isNaN(date)) {
    const ms = date - Date.now();
    return ms > 0 ? ms : 0;
  }
}

/**
 * Sleep helper with optional jitter.
 * @param {number} ms
 * @param {boolean} jitter
 */
function sleep(ms, jitter = true) {
  if (ms <= 0) return Promise.resolve();
  const jittered = jitter ? Math.round(ms * (0.8 + Math.random() * 0.4)) : ms;
  return new Promise((r) => setTimeout(r, jittered));
}

/**
 * Connect a user-provided AbortSignal to our controller, so either can cancel.
 * @param {AbortController} controller
 * @param {AbortSignal | undefined} userSignal
 * @returns {() => void} cleanup
 */
function linkAbortSignals(controller, userSignal) {
  if (!userSignal) return () => {};
  const onAbort = () => {
    if (!controller.signal.aborted) {
      try {
        controller.abort(userSignal.reason ?? new DOMException('Aborted', 'AbortError'));
      } catch {
        controller.abort();
      }
    }
  };
  if (userSignal.aborted) onAbort();
  userSignal.addEventListener('abort', onAbort, { once: true });
  return () => userSignal.removeEventListener('abort', onAbort);
}

/**
 * Core fetch with timeout + retry.
 * @param {string} url
 * @param {RequestInit & { method?: string }} options
 * @param {{ timeoutMs?: number, retries?: number, backoffBaseMs?: number, backoffFactor?: number }} cfg
 * @returns {Promise<Response>}
 */
async function fetchWithTimeoutAndRetry(url, options, cfg = {}) {
  const {
    timeoutMs = 15000,
    retries = 2,
    backoffBaseMs = 400,
    backoffFactor = 2,
  } = cfg;

  let attempt = 0;
  let lastError;

  while (attempt <= retries) {
    const controller = new AbortController();
    const cleanupLink = linkAbortSignals(controller, options.signal);

    const timeoutId = setTimeout(() => {
      // Timeout: abort request
      try {
        controller.abort(new DOMException('Timeout', 'AbortError'));
      } catch {
        controller.abort();
      }
    }, timeoutMs);

    try {
      const res = await fetch(url, { ...options, signal: controller.signal });
      clearTimeout(timeoutId);
      cleanupLink();

      if (res.ok) return res;

      // Non-OK: read body safely and maybe retry if transient
      const { data } = await parseBody(res);

      if (isTransientStatus(res.status) && attempt < retries) {
        const retryAfter = parseRetryAfterMs(res.headers);
        const delay = retryAfter ?? backoffBaseMs * Math.pow(backoffFactor, attempt);
        await sleep(delay, true);
        attempt += 1;
        continue;
      }

      const meta = {
        status: res.status,
        statusText: res.statusText,
        url,
        method: (options.method || 'GET').toUpperCase(),
        requestId: res.headers.get('x-request-id') || res.headers.get('x-requestid') || undefined,
        headers: headersToObject(res.headers),
        body: data,
      };
      const message = `HTTP ${meta.status} ${meta.statusText} for ${meta.method} ${meta.url}`;
      throw new HTTPError(extractErrorMessage(data) || message, meta);
    } catch (err) {
      clearTimeout(timeoutId);
      cleanupLink();

      // Abort or network errors might be retryable
      const isAbort = err && typeof err === 'object' && err.name === 'AbortError';
      const isNetwork = err instanceof TypeError && !isAbort; // fetch network error in browsers/Node often TypeError

      const canRetry = (isAbort || isNetwork) && attempt < retries;
      lastError = err;

      if (canRetry) {
        const delay = backoffBaseMs * Math.pow(backoffFactor, attempt);
        await sleep(delay, true);
        attempt += 1;
        continue;
      }
      throw err;
    }
  }

  // Exceeded attempts
  throw lastError;
}

/**
 * Try to extract a meaningful error message from server payload.
 * @param {any} data
 */
function extractErrorMessage(data) {
  if (!data) return '';
  if (typeof data === 'string') return data.slice(0, 500);
  if (typeof data.message === 'string') return data.message;
  if (data.error && typeof data.error === 'object') {
    if (typeof data.error.message === 'string') return data.error.message;
    if (typeof data.error.code === 'string') return `${data.error.code}`;
  }
  return '';
}

/**
 * Build headers for JSON request with Bearer auth.
 * @param {string} token
 * @param {Record<string, string>} [extra]
 */
function buildJSONHeaders(token, extra = {}) {
  const headers = new Headers(extra);
  if (token) headers.set('Authorization', `Bearer ${token}`);
  headers.set('Accept', 'application/json');
  headers.set('Content-Type', 'application/json');
  return headers;
}

/**
 * Safe JSON stringify that drops undefined and functions.
 * @param {any} obj
 */
function safeStringify(obj) {
  return JSON.stringify(
    obj,
    (_, v) => {
      if (typeof v === 'function' || typeof v === 'undefined') return undefined;
      if (typeof v === 'bigint') return v.toString();
      return v;
    }
  );
}

/**
 * Generate text via ExampleAI API.
 * @param {{
 *   token: string,                 // Bearer token (do not hardcode in client)
 *   payload: Record<string, any>,  // Request JSON body expected by the API
 *   timeoutMs?: number,
 *   retries?: number,
 *   fetchOptions?: Partial<RequestInit> // e.g., { signal, integrity, referrerPolicy }
 * }} params
 * @returns {Promise<{ data: any, response: Response }>}
 */
export async function generateText({ token, payload, timeoutMs = 15000, retries = 2, fetchOptions = {} }) {
  if (!token || typeof token !== 'string') {
    throw new Error('Missing Bearer token. Provide a valid token via "token" parameter.');
  }
  if (!payload || typeof payload !== 'object') {
    throw new Error('Invalid payload. Provide a JSON-serializable object in "payload".');
  }

  const url = 'https://api.exampleai.com/v1/text/generate';

  const options = {
    method: 'POST',
    // Never cache POST requests with credentials; rely on server-side caching if any
    cache: 'no-store',
    credentials: 'omit', // Since we use Bearer token, do not send cookies by default
    redirect: 'follow',
    keepalive: false,
    headers: buildJSONHeaders(token, fetchOptions.headers || {}),
    body: safeStringify(payload),
    // Allow caller-provided options like signal, referrerPolicy, integrity
    ...fetchOptions,
    // Ensure our headers/body override fetchOptions in case of conflicts
    headers: buildJSONHeaders(token, fetchOptions.headers || {}),
    body: safeStringify(payload),
  };

  const res = await fetchWithTimeoutAndRetry(url, options, {
    timeoutMs,
    retries,
    backoffBaseMs: 400,
    backoffFactor: 2,
  });

  const { data } = await parseBody(res);
  return { data, response: res };
}

// Optional: small convenience wrapper that returns only parsed data
export async function generateTextData(params) {
  const { data } = await generateText(params);
  return data;
}

参数说明

  • token
    • 类型:string
    • 说明:用于认证的 Bearer Token。请从安全来源注入(例如服务端下发、环境变量),不要在前端源码中硬编码。
  • payload
    • 类型:Record<string, any>
    • 说明:API 请求体(JSON)。字段应遵循 ExampleAI 的接口定义(如 prompt、model、max_tokens、temperature 等)。
  • timeoutMs
    • 类型:number,默认 15000
    • 说明:请求超时时间(毫秒)。到达超时会自动中止请求并按策略重试。
  • retries
    • 类型:number,默认 2
    • 说明:瞬时错误(429/408/5xx、网络故障、超时)时的最大重试次数。
  • fetchOptions
    • 类型:Partial
    • 说明:透传给 fetch 的可选项(如 signal、referrerPolicy、integrity、headers 等)。注意:headers、body 会由库正确覆盖以确保 JSON 和鉴权一致。
  • 内置头部
    • Authorization: Bearer
    • Content-Type: application/json
    • Accept: application/json
  • 其他请求选项
    • cache: 'no-store':避免对 POST 请求进行浏览器缓存
    • credentials: 'omit':不发送 cookie,避免与 Bearer 混用造成风险
    • redirect: 'follow':遵循重定向
    • keepalive: false:避免在页面卸载时的遗留请求问题

使用示例

  • 浏览器(注意不要在前端硬编码 Token)
import { generateTextData } from './exampleai.js';

// token 由服务端安全下发(例如通过 HTTP-only Cookie 换取临时 Bearer,或在 SSR 中注入)
const token = window.__EXAMPLEAI_TOKEN__;

const data = await generateTextData({
  token,
  payload: {
    model: 'exampleai-text-1',
    prompt: 'Write a haiku about autumn wind.',
    max_tokens: 128,
    temperature: 0.7,
    // user: 'user-123', // 如 API 支持用户追踪,可传
  },
  timeoutMs: 12000,
  retries: 2,
});
console.log('Generated:', data);
  • Node.js >= 18
import { generateText } from './exampleai.js';

const token = process.env.EXAMPLEAI_API_KEY; // 不要硬编码在代码仓库

try {
  const { data, response } = await generateText({
    token,
    payload: {
      model: 'exampleai-text-1',
      prompt: 'Summarize the benefits of typed JavaScript.',
      max_tokens: 200,
      temperature: 0.4,
    },
    timeoutMs: 15000,
    retries: 3,
    fetchOptions: {
      // 可选:绑定外部取消信号
      // signal: abortController.signal,
      referrerPolicy: 'no-referrer',
    },
  });

  console.log('status:', response.status);
  console.log('result:', data);
} catch (err) {
  // 详见下方“错误处理”
  console.error(err);
}
  • 绑定取消信号(用户手动中断)
const ac = new AbortController();
const p = generateTextData({
  token,
  payload: { model: 'exampleai-text-1', prompt: '...' },
  fetchOptions: { signal: ac.signal },
});
setTimeout(() => ac.abort(), 3000); // 3 秒后主动取消
await p; // 将抛出 AbortError

注意事项

  • 认证与安全
    • 不要在前端仓库硬编码 API Key。推荐由后端安全签发临时 Token 或通过服务端代理调用。
    • 使用 Bearer Token 时设置 credentials: 'omit',避免携带 Cookie 混淆身份。
  • CORS
    • 浏览器环境下,跨域请求需服务端正确设置 Access-Control-Allow-Origin 等 CORS 头。代码不会绕过 CORS。
  • 超时与重试
    • 已对 429/408/5xx、网络错误与超时启用指数退避重试;非幂等接口请谨慎评估是否开启重试。
    • 若服务端返回 Retry-After,将优先遵循该延迟。
  • 数据序列化
    • 自动以 application/json 发送请求体;undefined、function 将被安全丢弃,BigInt 转为字符串。
  • 资源清理
    • 超时和用户取消都会触发 AbortController,避免悬挂请求。
  • 兼容性
    • 需要现代浏览器或 Node.js >= 18(内置 fetch/AbortController)。

错误处理

  • 错误类型
    • HTTPError:服务器返回非 2xx
      • 属性:status、statusText、url、method、headers、requestId、body(解析后的错误负载或文本)
      • 典型场景:400 参数错误、401/403 鉴权失败、404 资源不存在、429 限流、5xx 服务器错误
    • AbortError:请求被超时或手动取消
    • TypeError:网络故障(DNS、连接中断等,浏览器/Node 常用)
  • 建议的捕获与分支
try {
  const { data } = await generateText({ token, payload });
  // 使用 data
} catch (err) {
  if (err instanceof HTTPError) {
    // 服务器返回的详细错误
    console.error('HTTPError:', {
      status: err.status,
      requestId: err.requestId,
      body: err.body,
    });
    if (err.status === 401 || err.status === 403) {
      // 引导刷新凭证或跳转登录
    } else if (err.status === 429) {
      // 可提示用户稍后重试
    }
  } else if (err && err.name === 'AbortError') {
    console.warn('Request aborted (timeout or user cancel)');
  } else {
    // 网络错误或其他未知异常
    console.error('Network/Unknown error:', err);
  }
}
  • 日志与追踪
    • 如果服务端提供 X-Request-ID,请记录 err.requestId 以便问题排查。
    • 在生产环境避免把完整 token 打到日志。只记录必要的元数据。

代码实现

/**
 * 安全、健壮的 GET + URLSearchParams + API Key 认证的 fetch 实现
 * - 使用 AbortController 实现超时控制
 * - 支持网络错误与 429/5xx 的指数重试(带抖动),遵循 Retry-After
 * - 对响应进行内容类型探测并安全解析(JSON/Text)
 * - 不硬编码 API Key,支持自定义认证头与认证前缀
 */

class TimeoutError extends Error {
  constructor(message = 'Request timed out') {
    super(message);
    this.name = 'TimeoutError';
  }
}

class HTTPError extends Error {
  constructor(response, body) {
    super(`HTTP ${response.status} ${response.statusText}`);
    this.name = 'HTTPError';
    this.status = response.status;
    this.statusText = response.statusText;
    this.body = body; // 服务器返回的错误主体(已解析)
  }
}

/**
 * 将对象或 URLSearchParams 规范化为 URLSearchParams
 * - 忽略 undefined/null
 * - 对数组值进行多次 append(key, v)
 */
function toURLSearchParams(input) {
  if (input instanceof URLSearchParams) return input;
  const usp = new URLSearchParams();
  if (input && typeof input === 'object') {
    for (const [k, v] of Object.entries(input)) {
      if (v === undefined || v === null) continue;
      if (Array.isArray(v)) {
        for (const item of v) {
          if (item !== undefined && item !== null) usp.append(k, String(item));
        }
      } else {
        usp.append(k, String(v));
      }
    }
  }
  return usp;
}

/**
 * 解析响应体:优先 JSON,其次 Text;其他类型将回退为 Text
 */
async function parseBody(response) {
  const contentType = (response.headers.get('content-type') || '').toLowerCase();
  try {
    if (contentType.includes('application/json')) {
      return await response.json();
    }
    if (contentType.startsWith('text/')) {
      return await response.text();
    }
    // 非常规类型回退到 text(避免在不同运行时下的 Blob/ArrayBuffer 差异)
    return await response.text();
  } catch {
    // 解析失败时保证不会再次抛错
    return null;
  }
}

/**
 * 解析 Retry-After 头部(秒或 HTTP 日期)
 */
function parseRetryAfter(retryAfter) {
  if (!retryAfter) return null;
  const secMatch = retryAfter.trim().match(/^\d+$/);
  if (secMatch) return parseInt(secMatch[0], 10) * 1000;
  const date = Date.parse(retryAfter);
  if (!Number.isNaN(date)) {
    const ms = date - Date.now();
    return ms > 0 ? ms : 0;
  }
  return null;
}

/**
 * 计算重试延时(指数退避 + 抖动),并尊重 Retry-After
 */
function computeRetryDelay({ base = 500, cap = 5000, attempt = 0, response }) {
  // 优先使用服务端的 Retry-After
  if (response && (response.status === 429 || response.status === 503)) {
    const retryAfter = response.headers.get('retry-after');
    const fromHeader = parseRetryAfter(retryAfter);
    if (fromHeader !== null) return Math.min(fromHeader, 30_000); // 保护上限 30s
  }
  const exp = Math.min(cap, base * Math.pow(2, attempt)); // 指数退避
  const jitter = Math.floor(Math.random() * 250); // 0-250ms 抖动
  return exp + jitter;
}

/**
 * 判断是否为“可能可重试”的网络层错误
 * - 浏览器中 fetch 的网络错误常为 TypeError
 */
function isLikelyTransientNetworkError(err) {
  return err && (err.name === 'FetchError' || err instanceof TypeError);
}

/**
 * GET 请求封装:对 https://text.example.net/summarize 发起请求
 * @param {Object} options
 * @param {Object|URLSearchParams} options.params - 查询参数(将被编码为 URLSearchParams)
 * @param {string} options.apiKey - API Key(不要在前端硬编码,建议运行时安全注入)
 * @param {string} [options.apiKeyHeader='Authorization'] - 认证头名称(如 'Authorization' 或 'X-API-Key')
 * @param {string|null} [options.apiKeyScheme='Bearer'] - 认证前缀(如 'Bearer';传 null 表示不加前缀)
 * @param {number} [options.timeout=10000] - 超时时间(毫秒)
 * @param {number} [options.retries=2] - 最大重试次数(仅对网络错误/超时/特定 4xx/5xx 生效)
 * @param {number} [options.retryBaseDelay=500] - 初始重试基准延时(毫秒)
 * @param {string} [options.baseURL='https://text.example.net/summarize'] - 基础 URL
 * @param {AbortSignal} [options.signal] - 外部取消信号
 * @param {Function} [options.fetchImpl] - 自定义 fetch 实现(默认取全局 fetch)
 * @returns {Promise<{ data: any, response: Response }>}
 */
async function fetchSummarize({
  params,
  apiKey,
  apiKeyHeader = 'Authorization',
  apiKeyScheme = 'Bearer',
  timeout = 10_000,
  retries = 2,
  retryBaseDelay = 500,
  baseURL = 'https://text.example.net/summarize',
  signal,
  fetchImpl,
} = {}) {
  const fetchFn = fetchImpl || (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : null);
  if (!fetchFn) {
    throw new Error('Global fetch is not available. Provide options.fetchImpl in non-browser environments.');
  }

  const usp = toURLSearchParams(params);
  const url = new URL(baseURL);
  // 保留原有查询的基础上合并(若 baseURL 已带查询参数)
  if (url.search) {
    const existing = new URLSearchParams(url.search);
    for (const [k, v] of usp.entries()) existing.append(k, v);
    url.search = existing.toString();
  } else {
    url.search = usp.toString();
  }

  const headers = new Headers({
    Accept: 'application/json, text/plain;q=0.8, */*;q=0.5',
  });

  if (apiKey) {
    const value = apiKeyScheme ? `${apiKeyScheme} ${apiKey}` : apiKey;
    headers.set(apiKeyHeader, value);
  }

  // 构建超时控制
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(new TimeoutError(`Request timeout after ${timeout}ms`)), timeout);
  // 将外部 signal 的取消传递到内部
  if (signal) {
    if (signal.aborted) {
      controller.abort(signal.reason);
    } else {
      signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
    }
  }

  const requestInit = {
    method: 'GET',
    headers,
    // 对跨域严格遵守 CORS,不做任何绕过行为
    mode: 'cors',
    cache: 'no-store',
    credentials: 'omit', // 如需发送跨站 Cookie,请改为 'include' 且确保服务器允许
    redirect: 'follow',
    signal: controller.signal,
  };

  const RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);

  let attempt = 0;
  try {
    while (true) {
      try {
        const response = await fetchFn(url.toString(), requestInit);

        if (!response.ok) {
          const errorBody = await parseBody(response);
          const httpError = new HTTPError(response, errorBody);

          if (attempt < retries && RETRYABLE_STATUS.has(response.status)) {
            const delay = computeRetryDelay({
              base: retryBaseDelay,
              attempt,
              response,
            });
            await new Promise((r) => setTimeout(r, delay));
            attempt += 1;
            continue;
          }
          throw httpError;
        }

        const data = await parseBody(response);
        return { data, response };
      } catch (err) {
        // AbortError 与 TimeoutError 不默认重试(可按需调整)
        if (err instanceof TimeoutError || err?.name === 'AbortError') {
          if (attempt < retries) {
            const delay = computeRetryDelay({ base: retryBaseDelay, attempt });
            await new Promise((r) => setTimeout(r, delay));
            attempt += 1;
            continue;
          }
          throw err;
        }

        // 其他网络层错误可有限次重试
        if (isLikelyTransientNetworkError(err) && attempt < retries) {
          const delay = computeRetryDelay({ base: retryBaseDelay, attempt });
          await new Promise((r) => setTimeout(r, delay));
          attempt += 1;
          continue;
        }

        throw err;
      }
    }
  } finally {
    clearTimeout(timeoutId);
  }
}

// 可选:导出默认函数(在模块环境下)
// export default fetchSummarize;

参数说明

  • params:查询参数
    • 类型:Object 或 URLSearchParams
    • 作用:将被序列化到 GET URL 的 query string 中;会自动跳过 undefined/null,数组会生成重复键
  • apiKey:API Key
    • 类型:string
    • 作用:用于认证,不要在前端硬编码。建议运行时从安全位置注入或通过后端代理
  • apiKeyHeader:认证头名称
    • 默认值:Authorization
    • 说明:也可使用 'X-API-Key' 等,自行与服务端约定
  • apiKeyScheme:认证前缀
    • 默认值:Bearer
    • 说明:传 null 则不添加前缀,直接发送原始 apiKey
  • timeout:超时时间(毫秒)
    • 默认值:10000
    • 说明:超时将触发 Abort 并抛出 TimeoutError
  • retries:最大重试次数
    • 默认值:2
    • 说明:对网络错误、超时、以及 408/429/5xx 进行有限次指数退避重试
  • retryBaseDelay:初始重试基准延时(毫秒)
    • 默认值:500
    • 说明:实际延时为指数退避并带抖动,且在 429/503 时优先遵循 Retry-After
  • baseURL:请求基础地址
  • signal:AbortSignal
    • 用于外部主动取消请求(例如用户导航离开页面)
  • fetchImpl:自定义 fetch 函数
    • 默认使用全局 fetch;在非浏览器环境(Node < 18)可传入 node-fetch 等实现

使用示例

  • 浏览器(前端通过后端代理注入临时令牌,示例仅示意):
// 推荐:从安全的后端接口获取短期令牌或使用同源后端代理,而非将长效 API Key 暴露在浏览器
async function getRuntimeToken() {
  // 例如:从同源后端换取一次性/短期 token
  const res = await fetch('/api/token', { credentials: 'same-origin' });
  if (!res.ok) throw new Error('Failed to get token');
  const { token } = await res.json();
  return token;
}

(async () => {
  const token = await getRuntimeToken();

  const params = new URLSearchParams({
    text: '在这里放需要摘要的文本内容',
    max_sentences: '3',
    lang: 'zh',
  });

  try {
    const { data, response } = await fetchSummarize({
      params,
      apiKey: token,
      apiKeyHeader: 'Authorization',
      apiKeyScheme: 'Bearer',
      timeout: 8000,
      retries: 2,
    });

    console.log('Status:', response.status);
    console.log('Data:', data);
  } catch (err) {
    // 参考文末“错误处理”部分
    console.error('Summarize request failed:', err);
  }
})();
  • Node.js 18+(服务端调用,使用环境变量注入):
// Node 18+ 原生支持 fetch。确保在服务端持有并保护 API Key。
import fs from 'node:fs'; // 仅示例:可用于读取文件或日志等

(async () => {
  const params = {
    text: '面向生产环境的稳健 fetch 封装示例',
    max_sentences: 2,
  };

  try {
    const { data } = await fetchSummarize({
      params,
      apiKey: process.env.TEXT_API_KEY, // 从环境变量注入
      apiKeyHeader: 'X-API-Key',        // 假设服务端采用该头部
      apiKeyScheme: null,               // 不加 Bearer 前缀,直接发送 key 值
      timeout: 10000,
      retries: 3,
    });

    console.log('Summarize:', data);
  } catch (err) {
    console.error('Server-side summarize failed:', err);
  }
})();
  • 取消请求(例如用户切换页面时):
const controller = new AbortController();

fetchSummarize({
  params: { text: '需要摘要的文本' },
  apiKey: await getRuntimeToken(),
  signal: controller.signal,
}).catch((err) => {
  if (err.name === 'AbortError') {
    console.log('Request cancelled');
  } else {
    console.error(err);
  }
});

// 某些条件满足时取消
controller.abort();

注意事项

  • 切勿在浏览器代码中硬编码长效 API Key
    • 建议通过后端代理或短期令牌(Token Exchange)模式,减少泄露风险
  • CORS
    • 代码使用 mode: 'cors',需后端正确设置 CORS 响应头(如 Access-Control-Allow-Origin 等)
    • 请不要尝试通过前端代码绕过 CORS
  • GET 方法不应携带请求体
    • 本实现将所有参数编码到 URL 查询字符串中
  • 超时与重试
    • 超时会触发 Abort;默认对 408/429/5xx 及网络错误进行指数重试,429/503 优先遵循 Retry-After
    • 若后端具备强限流,请合理设置重试次数与延时,避免放大流量
  • 数据解析
    • 优先解析 JSON;若为文本类型则返回 string;其他类型回退到文本
    • 如需二进制数据处理,请根据后端返回的 Content-Type 定制解析逻辑
  • 安全与日志
    • 记录错误日志时勿打印完整响应主体中的敏感信息
    • 在生产环境中请妥善处理异常,避免将内部错误细节暴露给终端用户

错误处理

  • TimeoutError
    • 表示请求因超时被中止。可提示用户“请求超时”,或适当重试
  • AbortError
    • 表示请求被主动取消(用户切换页面、组件卸载等)
  • HTTPError
    • 包含 status、statusText、body(已解析)
    • 常见场景:
      • 400/422:参数校验错误,检查 params 是否完整、格式是否正确
      • 401/403:认证失败或权限不足,检查 API Key 是否有效、头部是否正确
      • 404:接口或资源不存在
      • 429:被限流。实现已支持按 Retry-After 重试,仍失败则应降频或提示用户
      • 5xx:服务器错误,稍后重试或联系后端
  • 网络错误(TypeError/FetchError)
    • 可能是断网、DNS 问题或被 CORS 拒绝(浏览器中通常表现为网络错误)
    • 实现默认会有限次重试;持续失败需检查网络与跨域配置

示例捕获:

try {
  const result = await fetchSummarize({ params: { text: '...' }, apiKey: await getRuntimeToken() });
  console.log(result.data);
} catch (err) {
  if (err instanceof TimeoutError) {
    // 提示超时
  } else if (err.name === 'AbortError') {
    // 用户取消
  } else if (err instanceof HTTPError) {
    // 根据 err.status 做分支
    console.error('HTTP', err.status, err.body);
  } else {
    // 其他网络/运行时错误
    console.error('Unexpected error', err);
  }
}

代码实现

/**
 * PATCH text to the compose/continue endpoint with Basic Auth, robust timeout and error handling.
 *
 * Target: https://writer.example.org/v2/compose/continue
 * Method: PATCH
 * Request Body: text/plain; charset=utf-8
 * Auth: Basic (username:password), sent in Authorization header
 *
 * This function is runtime-agnostic: works in modern browsers and Node.js 18+ (native fetch).
 */

/**
 * Encode "username:password" to Base64 (UTF-8 safe) for Basic Auth.
 * Avoids btoa non-ASCII issues; uses Buffer in Node or TextEncoder+btoa in browsers.
 */
function toBase64Utf8(str) {
  // Node.js path
  if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
    return Buffer.from(str, 'utf-8').toString('base64');
  }
  // Browser path
  const encoder = new TextEncoder();
  const bytes = encoder.encode(str);
  let binary = '';
  const chunkSize = 0x8000; // prevent call stack overflow on large payloads
  for (let i = 0; i < bytes.length; i += chunkSize) {
    binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
  }
  if (typeof btoa === 'function') {
    return btoa(binary);
  }
  throw new Error('Base64 encoding is not supported in this environment.');
}

/**
 * Build the Basic Authorization header value.
 */
function buildBasicAuthHeader(username, password) {
  if (!username || !password) {
    throw new Error('Missing Basic Auth credentials: username and password are required.');
  }
  const token = toBase64Utf8(`${username}:${password}`);
  return `Basic ${token}`;
}

/**
 * Parse HTTP response into { data, raw, contentType }.
 * - JSON -> object
 * - Text/others -> string
 * - 204/205 -> null data
 */
async function parseResponse(response) {
  const contentType = response.headers.get('content-type') || '';
  if (response.status === 204 || response.status === 205) {
    return { data: null, raw: '', contentType };
  }
  try {
    if (contentType.includes('application/json')) {
      const json = await response.json();
      return { data: json, raw: JSON.stringify(json), contentType };
    }
    const text = await response.text();
    return { data: text, raw: text, contentType };
  } catch (e) {
    // Fallback: treat as text if parsing fails
    const text = await response.text().catch(() => '');
    return { data: text, raw: text, contentType };
  }
}

/**
 * Create a normalized error for non-2xx HTTP responses and network/timeout failures.
 */
function createHttpError({ message, name = 'HTTPError', response, url, body, cause }) {
  const err = new Error(message);
  err.name = name;
  if (response) {
    err.status = response.status;
    err.statusText = response.statusText;
  }
  err.url = url;
  if (body !== undefined) err.body = body; // parsed body (json or text)
  if (cause) err.cause = cause;
  return err;
}

/**
 * PATCH text to a URL with Basic Auth using fetch.
 *
 * @param {Object} params
 * @param {string} params.url - Target URL (e.g., https://writer.example.org/v2/compose/continue)
 * @param {string} params.text - Plain text payload to send in request body
 * @param {string} params.username - Basic auth username
 * @param {string} params.password - Basic auth password
 * @param {number} [params.timeout=15000] - Timeout in ms (abort the request if exceeded)
 * @param {Object} [params.extraHeaders] - Optional additional headers (won’t override required ones)
 * @returns {Promise<{status:number, ok:boolean, headers:Headers, data:any, contentType:string|null}>}
 */
async function patchTextWithBasicAuth({
  url,
  text,
  username,
  password,
  timeout = 15000,
  extraHeaders = {},
}) {
  if (!url) throw new Error('Parameter "url" is required.');
  if (typeof text !== 'string') {
    throw new Error('Parameter "text" must be a string for text/plain payload.');
  }

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeout);

  // Build headers
  const headers = new Headers({
    'Content-Type': 'text/plain; charset=utf-8',
    Accept: 'application/json, text/plain;q=0.9, */*;q=0.8',
    Authorization: buildBasicAuthHeader(username, password),
    ...extraHeaders, // allow safe extension (won’t override above keys if extraHeaders uses same case)
  });

  const requestInit = {
    method: 'PATCH',
    headers,
    body: text,
    // CORS-safe defaults. Do not send cookies by default for cross-origin.
    mode: 'cors',
    credentials: 'omit',
    redirect: 'follow',
    cache: 'no-store',
    signal: controller.signal,
  };

  let response;
  try {
    response = await fetch(url, requestInit);
  } catch (cause) {
    clearTimeout(timer);
    if (cause && cause.name === 'AbortError') {
      throw createHttpError({
        message: `Request timed out after ${timeout} ms`,
        name: 'TimeoutError',
        url,
        cause,
      });
    }
    throw createHttpError({
      message: 'Network error or CORS failure while performing fetch()',
      name: 'NetworkError',
      url,
      cause,
    });
  } finally {
    clearTimeout(timer);
  }

  const { data, raw, contentType } = await parseResponse(response);

  if (!response.ok) {
    // Construct a meaningful message with snippets (avoid logging sensitive data)
    const snippet =
      typeof data === 'string'
        ? data.slice(0, 300)
        : JSON.stringify(data).slice(0, 300);

    let friendly = `HTTP ${response.status} ${response.statusText}`;
    if (response.status === 401) friendly += ' (authentication failed)';
    if (response.status === 403) friendly += ' (forbidden)';
    if (response.status === 429) friendly += ' (rate limited)';
    if (response.status >= 500) friendly += ' (server error)';

    const err = createHttpError({
      message: `${friendly}. URL: ${url}`,
      response,
      url,
      body: data,
    });
    err.contentType = contentType || null;
    err.bodyPreview = snippet;
    throw err;
  }

  return {
    status: response.status,
    ok: response.ok,
    headers: response.headers,
    data,
    contentType: contentType || null,
  };
}

参数说明

  • url: 请求地址。示例:https://writer.example.org/v2/compose/continue
  • method: 固定为 PATCH。
  • headers:
    • Content-Type: text/plain; charset=utf-8,声明请求体为纯文本并使用 UTF-8 编码。
    • Accept: 客户端期望的响应格式;优先 JSON,其次文本。
    • Authorization: Basic base64(username:password),使用 UTF-8 安全的 Base64 编码生成。
  • body: 字符串类型的纯文本数据。
  • mode: 'cors',跨域场景采用标准 CORS 流程。
  • credentials: 'omit',不自动携带 Cookie,降低跨域请求带来的安全风险。
  • cache: 'no-store',避免缓存 PATCH 响应。
  • redirect: 'follow',跟随重定向。
  • signal: AbortController.signal,实现超时与主动中断。
  • timeout: 超时时间(毫秒),默认 15000。

使用示例

(async () => {
  // 请从安全来源注入凭据(示例中为占位值)
  const USERNAME = '<your-username>'; // e.g., process.env.API_USER in Node
  const PASSWORD = '<your-password>'; // e.g., process.env.API_PASS in Node

  const url = 'https://writer.example.org/v2/compose/continue';
  const textPayload = 'Continue writing from the previous draft.\nPlease add a concluding paragraph.';

  try {
    const result = await patchTextWithBasicAuth({
      url,
      text: textPayload,
      username: USERNAME,
      password: PASSWORD,
      timeout: 12000,
      extraHeaders: {
        // 可选:例如自定义追踪 ID(非敏感)
        'X-Request-ID': 'req-' + Date.now(),
      },
    });

    // 根据响应类型处理
    if (result.contentType?.includes('application/json')) {
      console.log('JSON response:', result.data);
    } else {
      console.log('Text response:', result.data);
    }
  } catch (err) {
    // 统一错误处理
    console.error(`[${err.name}] ${err.message}`);
    if (err.status) {
      console.error('Status:', err.status, err.statusText);
      console.error('Body preview:', err.bodyPreview);
    }
    // 可根据 err.name 分流:TimeoutError / NetworkError / HTTPError
  }
})();

注意事项

  • 使用 HTTPS:Basic Auth 必须在 HTTPS 之上使用,避免凭据明文泄露。
  • 凭据管理:
    • 不要在代码库中硬编码用户名/密码;在 Node 中通过环境变量或安全密钥管理服务注入。
    • 浏览器端若必须使用 Basic Auth,请确保页面来源与后端协商好 CORS,并避免将凭据长期存储在 LocalStorage 等持久化位置。
  • CORS 合规:
    • 服务器应返回正确的 CORS 响应头(如 Access-Control-Allow-Origin 等)。代码未尝试绕过 CORS。
    • 避免使用 no-cors 模式,因为它会导致不可见的 opaque 响应,无法读取状态码/内容。
  • 请求体与编码:
    • 已设置 text/plain; charset=utf-8,适合包含多语言字符与表情符号。
  • 重试策略:
    • PATCH 通常非幂等,不建议默认自动重试;如果要重试,请与服务端协商幂等性或使用请求幂等键。
  • 日志审计:
    • 不要在日志中打印完整响应体和敏感头信息;示例中仅截取了预览片段。

错误处理

  • 超时错误(TimeoutError):
    • 使用 AbortController 强制终止请求;可通过 timeout 参数调整。
  • 网络错误(NetworkError):
    • 包括 DNS 解析失败、连接失败、或被浏览器的 CORS 策略拦截。错误对象会包含 cause 以便诊断。
  • HTTP 状态错误(HTTPError):
    • 非 2xx 状态时解析响应体(JSON 或文本),并在错误对象上附带 status、statusText、body、contentType、bodyPreview。
    • 常见场景:
      • 401: 认证失败(检查用户名/密码是否正确、服务端是否支持 Basic Auth)。
      • 403: 权限不足(检查授权策略)。
      • 429: 触发限流(增加退避、与服务端协商配额)。
      • 5xx: 服务端错误(记录请求 ID,与后端联调)。
  • 响应解析失败:
    • 若 JSON 解析失败,回退到文本解析,避免因格式异常导致的未捕获错误。
  • 清理与资源回收:
    • 不论成功或失败,都会清理超时计时器,防止泄漏。

示例详情

解决的问题

把“写网络请求”这件繁琐事,变成一次高质量的一键生成。通过让 AI 扮演资深 JS 工程师,快速产出可直接落地的 fetch 请求代码与说明,覆盖常见方法、认证、超时与错误兜底等关键场景,帮助你:1) 快速搭建稳定的接口层;2) 统一团队代码风格与错误处理策略;3) 显著减少重复劳动与低级问题;4) 降低首单接入与后续扩展成本;5) 以更安全、可维护的方式交付前端与数据交互能力。

适用用户

前端开发工程师

几分钟内搭建标准化请求模块,自动补齐超时、错误提示与数据解析,统一代码风格,减少线上故障。

全栈/独立开发者

快速对接支付、登录、地图等外部服务,一键生成多种方法请求与认证配置,把时间留给业务逻辑。

移动端H5与WebApp开发者

生成适配弱网的请求封装,内置超时与重试策略,轻松处理文件上传下载与分页加载,显著提升稳定性。

特征总结

一键生成符合现代规范的fetch请求,附带清晰注释与结构优化,复制即可用。
自动根据场景选择GET、POST等方法,处理请求头与请求体,减少手写细节。
内置错误捕获与状态判断,提供重试与友好提示,异常不再悄然漏过。
支持多种数据格式处理,发送与接收自动解析,避免格式不一致问题。
可配置超时与认证信息,兼容令牌与会话方式,安全省心更放心。
提供模板化参数与占位符,批量生成多种请求,快速覆盖多条业务线。
贴合前端工作流,支持文件上传下载与分页加载,常见场景即开即用。
代码风格统一且易维护,便于协作与评审,显著降低新人上手成本。
一键切换开发与生产配置,分离环境地址与调试开关,降低发布风险。

如何使用购买的提示词模板

1. 直接在外部 Chat 应用中使用

将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

2. 发布为 API 接口调用

把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

3. 在 MCP Client 中配置使用

在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

AI 提示词价格
¥20.00元
先用后买,用好了再付款,超安全!

您购买后可以获得什么

获得完整提示词模板
- 共 598 tokens
- 4 个可调节参数
{ 目标URL } { 请求方法 } { 数据格式 } { 认证方式 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
使用提示词兑换券,低至 ¥ 9.9
了解兑换券 →
限时半价

不要错过!

半价获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59