¥
立即购买

高质量函数生成

446 浏览
41 试用
11 购买
Nov 24, 2025更新

本提示词以专家视角生成高质量函数,支持指定参数、逻辑和返回值,自动考虑边界情况,并可选使用库函数或最佳实践,提升代码可维护性、可读性和执行效率,适用于前端及通用开发场景。

下面提供一个可直接使用的安全请求函数实现,包含内存缓存、超时与重试(指数/线性回退+抖动)、智能解析 JSON/文本、状态校验、大小写无关的头处理、取消能力,以及错误是否可重试的判断标记。

代码(JavaScript,带JSDoc类型注释):

// 模块级内存缓存(仅存成功结果)
const __safeFetchCache = new Map();

/**
 * 自定义错误,用于标记是否可重试、错误类型等
 */
class SafeFetchError extends Error {
  /**
   * @param {string} message
   * @param {object} opts
   * @param {boolean} opts.retriable
   * @param {number|undefined} opts.status
   * @param {string} opts.code // 'HTTP_STATUS'|'NETWORK'|'TIMEOUT'|'ABORTED'|'PARSER'
   * @param {string} opts.url
   * @param {number} opts.attempt
   * @param {any} [opts.cause]
   */
  constructor(message, opts) {
    super(message);
    this.name = 'SafeFetchError';
    this.retriable = !!opts.retriable;
    this.status = typeof opts.status === 'number' ? opts.status : undefined;
    this.code = opts.code;
    this.url = opts.url;
    this.attempt = opts.attempt;
    this.cause = opts.cause;
  }
}

/**
 * 合并多个 AbortSignal 为一个新的 signal
 * @param {AbortSignal[]} signals
 * @returns {{signal: AbortSignal, cleanup: () => void, controller: AbortController}}
 */
function anySignal(signals) {
  const controller = new AbortController();
  const listeners = [];
  const propagate = (s) => {
    try {
      // 若支持 abort(reason)
      controller.abort(s.reason ?? undefined);
    } catch {
      controller.abort();
    }
  };

  for (const s of signals) {
    if (!s) continue;
    if (s.aborted) {
      propagate(s);
      break;
    }
    const fn = () => propagate(s);
    s.addEventListener('abort', fn);
    listeners.push({ s, fn });
  }

  const cleanup = () => {
    for (const { s, fn } of listeners) {
      s.removeEventListener('abort', fn);
    }
    listeners.length = 0;
  };

  return { signal: controller.signal, cleanup, controller };
}

/**
 * 规范化响应头为小写键
 * @param {Headers} headers
 * @returns {Record<string,string>}
 */
function normalizeHeaders(headers) {
  const obj = {};
  headers.forEach((v, k) => {
    obj[String(k).toLowerCase()] = v;
  });
  return obj;
}

/**
 * 判断方法是否幂等
 * @param {string} method
 * @returns {boolean}
 */
function isIdempotent(method) {
  const m = String(method || '').toUpperCase();
  return m === 'GET' || m === 'PUT' || m === 'DELETE';
}

/**
 * 计算退避时间(ms)
 * @param {number} attempt 1-based
 * @param {'exponential'|'linear'} policy
 * @param {boolean} jitter
 * @returns {number}
 */
function computeBackoff(attempt, policy, jitter) {
  // 基础延迟:线性 300ms;指数 300 * 2^(attempt-1),上限 5s
  let base =
    policy === 'exponential'
      ? Math.min(300 * Math.pow(2, Math.max(0, attempt - 1)), 5000)
      : 300;
  if (jitter) {
    // 加入抖动:0~30% 基础延迟
    base += Math.floor(Math.random() * base * 0.3);
  }
  return base;
}

/**
 * 默认缓存 key(不含 body 的序列化以避免复杂场景;如需区分,请在 options.cache.key 指定)
 * @param {string} url
 * @param {string} method
 * @returns {string}
 */
function defaultCacheKey(url, method) {
  return `${String(method || '').toUpperCase()}:${url}`;
}

/**
 * URL 是否包含协议
 * @param {string} url
 */
function hasProtocol(url) {
  return /^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//.test(url);
}

/**
 * @typedef {Object} SafeFetchOptions
 * @property {'GET'|'POST'|'PUT'|'DELETE'|'PATCH'} [method]
 * @property {Record<string,string>} [headers]
 * @property {any} [body]
 * @property {number} [retries] // 0-5,默认2
 * @property {'exponential'|'linear'} [retryPolicy] // 默认 'exponential'
 * @property {boolean} [retryJitter] // 默认 true
 * @property {number} [timeoutMs] // 100-30000,默认 8000
 * @property {{enabled:boolean, ttlMs:number, key?:string}} [cache]
 * @property {boolean} [parseJson] // 默认 true
 * @property {number[]} [expectedStatus] // 默认 2xx
 * @property {AbortSignal} [signal]
 */

/**
 * 函数名称:safeFetchWithRetry
 * @param {string} url 必须包含协议
 * @param {SafeFetchOptions} [options]
 * @returns {Promise<{data:any, status:number, headers:Record<string,string>, fromCache:boolean, durationMs:number, url:string}>} 仅在成功时 resolve
 * 
 * 特性:
 * - 内存缓存(按 key 命中)
 * - 超时与取消(AbortController)
 * - 幂等方法指数/线性回退,带抖动;非幂等仅在网络错误时重试
 * - 智能解析 JSON 或文本;204/空响应返回 null
 * - 校验预期状态码(默认 2xx)
 * - 错误对象包含 retriable 标记与 code 用于判断是否可重试
 * - 返回的 Promise 附带 cancel() 方法:可主动取消
 */
function safeFetchWithRetry(url, options = {}) {
  if (typeof url !== 'string' || !hasProtocol(url)) {
    throw new Error('safeFetchWithRetry: url 必须是字符串且包含协议(如 https://)');
  }

  const method = String(options.method || 'GET').toUpperCase();
  if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
    throw new Error(`safeFetchWithRetry: 不支持的 method: ${method}`);
  }

  const headers = Object.assign({}, options.headers || {});
  const body = options.body;
  const retries = Math.max(0, Math.min(5, options.retries ?? 2));
  const retryPolicy = options.retryPolicy === 'linear' ? 'linear' : 'exponential';
  const retryJitter = options.retryJitter !== false; // 默认 true
  const timeoutMs = Math.max(100, Math.min(30000, options.timeoutMs ?? 8000));
  const parseJson = options.parseJson !== false; // 默认 true
  const expectedStatus = Array.isArray(options.expectedStatus) && options.expectedStatus.length > 0 ? options.expectedStatus : null;
  const cache = options.cache && typeof options.cache === 'object' ? options.cache : { enabled: false, ttlMs: 0 };
  const userSignal = options.signal;

  const idempotent = isIdempotent(method);

  const startTs = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();

  // 先查缓存
  const cacheEnabled = !!cache.enabled;
  const cacheKey = cacheEnabled ? (cache.key || defaultCacheKey(url, method)) : null;
  if (cacheEnabled && cacheKey) {
    const hit = __safeFetchCache.get(cacheKey);
    if (hit && hit.expires > Date.now()) {
      const durationMs = ((typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now()) - startTs;
      const result = {
        data: hit.value.data,
        status: hit.value.status,
        headers: hit.value.headers,
        fromCache: true,
        durationMs,
        url: hit.value.url
      };
      // 返回的 Promise 可附加 cancel,但缓存命中已经完成,无需取消
      const p = Promise.resolve(result);
      // 附加 cancel 空函数以保持接口一致性
      // @ts-ignore
      p.cancel = () => {};
      return p;
    } else if (hit && hit.expires <= Date.now()) {
      __safeFetchCache.delete(cacheKey);
    }
  }

  // 主控制器用于对外取消
  const mainController = new AbortController();

  let resolved = false;

  const promise = new Promise(async (resolve, reject) => {
    let attempt = 0;

    // 如果用户取消,传播到主控制器
    if (userSignal) {
      if (userSignal.aborted) {
        const err = new SafeFetchError('请求在开始前已被取消', {
          retriable: false,
          status: undefined,
          code: 'ABORTED',
          url,
          attempt: 0,
          cause: userSignal.reason
        });
        return reject(err);
      }
      userSignal.addEventListener('abort', () => {
        try { mainController.abort(userSignal.reason); } catch { mainController.abort(); }
      });
    }

    while (attempt <= retries) {
      attempt += 1;

      // 每次尝试使用独立的超时控制器
      const timeoutController = new AbortController();
      const timeoutId = setTimeout(() => {
        try { timeoutController.abort('timeout'); } catch { timeoutController.abort(); }
      }, timeoutMs);

      // 合并用户信号、主控制器与超时控制器
      const { signal: attemptSignal, cleanup, controller: combinedController } = anySignal([
        mainController.signal,
        userSignal,
        timeoutController.signal
      ]);

      let timedOut = false;
      const onAttemptAbort = () => {
        timedOut = timeoutController.signal.aborted && (timeoutController.signal.reason === 'timeout' || timeoutController.signal.reason === undefined);
      };
      attemptSignal.addEventListener('abort', onAttemptAbort);

      let res;
      try {
        res = await fetch(url, {
          method,
          headers,
          body,
          signal: attemptSignal,
          redirect: 'follow' // 默认行为
        });
      } catch (err) {
        // 清理
        attemptSignal.removeEventListener('abort', onAttemptAbort);
        cleanup();
        clearTimeout(timeoutId);

        // 区分错误类型
        const isAbort = err && err.name === 'AbortError';
        const wasUserAbort = isAbort && userSignal && userSignal.aborted && mainController.signal.aborted; // 用户触发
        const code = isAbort ? (timedOut ? 'TIMEOUT' : 'ABORTED') : 'NETWORK';

        // 非幂等方法仅在网络错误时重试(不含用户取消)
        const retriableNetwork = !wasUserAbort && (code === 'NETWORK' || code === 'TIMEOUT');
        const shouldRetry = idempotent ? retriableNetwork : (code === 'NETWORK' || code === 'TIMEOUT');

        if (shouldRetry && attempt <= retries) {
          const delay = idempotent ? computeBackoff(attempt, retryPolicy, retryJitter) : 0;
          if (delay > 0) {
            await new Promise((r) => setTimeout(r, delay));
          }
          continue;
        }

        const sfe = new SafeFetchError(
          code === 'TIMEOUT' ? `请求超时(${timeoutMs}ms)` : (code === 'ABORTED' ? '请求已取消' : '网络错误'),
          {
            retriable: retriableNetwork && attempt <= retries,
            status: undefined,
            code,
            url,
            attempt,
            cause: err
          }
        );
        return reject(sfe);
      }

      // 至此有响应
      try {
        // 清理
        attemptSignal.removeEventListener('abort', onAttemptAbort);
        cleanup();
        clearTimeout(timeoutId);

        const status = res.status;

        // 预期状态判断(默认 2xx)
        const statusOk = expectedStatus
          ? expectedStatus.includes(status)
          : status >= 200 && status <= 299;

        // 根据状态码是否需要重试(仅幂等)
        const retriableHttp = idempotent && (status === 408 || status === 429 || (status >= 500 && status <= 599));

        if (!statusOk) {
          if (retriableHttp && attempt <= retries) {
            const delay = computeBackoff(attempt, retryPolicy, retryJitter);
            await new Promise((r) => setTimeout(r, delay));
            continue;
          }
          const sfe = new SafeFetchError(`非预期状态码: ${status}`, {
            retriable: retriableHttp && attempt <= retries,
            status,
            code: 'HTTP_STATUS',
            url: res.url || url,
            attempt
          });
          return reject(sfe);
        }

        // 解析响应体
        const headersObj = normalizeHeaders(res.headers);
        const contentType = headersObj['content-type'] || '';
        const contentLength = headersObj['content-length'];
        const isNoContent = status === 204 || (contentLength === '0');

        let data = null;
        if (!isNoContent) {
          const isJsonLike = /\bjson\b/i.test(contentType) || /\+json\b/i.test(contentType);
          if (parseJson && isJsonLike) {
            try {
              data = await res.json();
            } catch (e) {
              const sfe = new SafeFetchError('JSON 解析失败', {
                retriable: false,
                status,
                code: 'PARSER',
                url: res.url || url,
                attempt,
                cause: e
              });
              return reject(sfe);
            }
          } else {
            // 文本解析
            data = await res.text();
            // 若为空串,按规范返回 null
            if (data === '') data = null;
          }
        }

        const endTs = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
        const durationMs = endTs - startTs;

        const result = {
          data,
          status,
          headers: headersObj,
          fromCache: false,
          durationMs,
          url: res.url || url
        };

        // 写缓存(仅成功结果)
        if (cacheEnabled && cacheKey && typeof cache.ttlMs === 'number' && cache.ttlMs > 0) {
          __safeFetchCache.set(cacheKey, {
            expires: Date.now() + cache.ttlMs,
            value: result
          });
        }

        resolved = true;
        return resolve(result);
      } catch (err) {
        // 兜底:解析或其他环节异常(通常不可重试)
        const sfe = new SafeFetchError('响应处理失败', {
          retriable: false,
          status: undefined,
          code: 'PARSER',
          url,
          attempt,
          cause: err
        });
        return reject(sfe);
      }
    }
  });

  // 暴露取消能力:为 Promise 附加 cancel 方法
  // @ts-ignore
  promise.cancel = () => {
    if (!resolved) {
      try { mainController.abort('cancelled'); } catch { mainController.abort(); }
    }
  };

  return promise;
}

// 附加工具:判断错误是否可重试
safeFetchWithRetry.isRetriableError = function(err) {
  return !!err && typeof err === 'object' && !!err.retriable;
};

// 可选:清理缓存或删除指定 key
safeFetchWithRetry.clearCache = function(key) {
  if (!key) {
    __safeFetchCache.clear();
  } else {
    __safeFetchCache.delete(key);
  }
};

// 可选:查看当前缓存大小
safeFetchWithRetry.cacheSize = function() {
  return __safeFetchCache.size;
};

说明与要点:

  • retries 默认 2 次,幂等方法(GET/PUT/DELETE)在网络错误、408、429、5xx 时按策略重试;非幂等(POST/PATCH)仅在网络错误或超时重试。
  • 超时触发 AbortController,错误 code 为 'TIMEOUT',并带 retriable 标记(幂等时会重试)。
  • 仅在成功时 resolve;错误会以 SafeFetchError reject,包含 retriable 布尔标记,便于调用方判断是否再次尝试。也可用 safeFetchWithRetry.isRetriableError(err)。
  • parseJson=true 且 Content-Type 包含 json 或 +json 时解析为 JSON,否则解析为文本;204 或空 content-length 返回 null。
  • 返回 Promise 附带 cancel() 方法,可主动取消进行中的请求;也可传入 options.signal 使用你自己的 AbortSignal。
  • 缓存只在成功时写入,命中缓存时 fromCache=true,durationMs 为本次调用耗时(非常短)。默认缓存 key 为 ${METHOD}:${url},如需更细粒度请传入 options.cache.key。
  • 头部键统一为小写,status 与最终响应 URL 一并返回。

下面提供一个符合要求的 JavaScript 函数 deepMergeSmart。默认不可变深度合并(clone=true),支持循环引用检测、可选路径级自定义合并、数组多策略、特殊类型处理、键顺序保持与在 clone=false 时尽量复用结构。

函数说明:

  • 参数:
    • target: object | array
    • sources: Array<object | array>
    • options:
      • arrayMerge: 'replace' | 'concat' | 'uniqueByValue',默认 'concat'
      • preferDefined: boolean,默认 true
      • clone: boolean,默认 true
      • maxDepth: number,默认 Infinity(根深度为 0)
      • customMerge: (path: Array<string|number>, a: any, b: any) => any | undefined(返回 undefined 表示不处理,使用默认策略;否则使用返回值)
  • 返回:新对象/数组,与输入相互独立(当 clone=true 时保证深度独立)。sources 为空时,返回 target 的深拷贝(clone=true)。

注意:

  • 路径 path 使用数组表示键路径,例如 ['theme','colors',0,'primary']。
  • 避免原型污染:跳过 'proto'、'constructor'、'prototype' 键。
  • Date:取最新(getTime 最大),RegExp:保留右值,Map/Set:按说明合并。
  • 循环引用:使用 WeakMap 防止死递归。
  • clone=false 时尽量复用未变更的子结构;clone=true 时返回的结构完全独立。

代码:

function deepMergeSmart(target, sources = [], options = {}) {
  const OPTS = {
    arrayMerge: 'concat', // 'replace' | 'concat' | 'uniqueByValue'
    preferDefined: true,
    clone: true,
    maxDepth: Infinity,
    customMerge: undefined,
    ...options,
  };

  if (!isObjectOrArray(target)) {
    throw new TypeError('target must be an object or array');
  }
  if (!Array.isArray(sources)) {
    throw new TypeError('sources must be an array');
  }

  // Keys to skip to avoid prototype pollution
  const POLLUTION_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

  // Cache for cloning to handle cycles
  const cloneCache = new WeakMap();

  // Merge with sequential folding
  let acc = target;
  if (sources.length === 0) {
    return OPTS.clone ? cloneDeep(target, cloneCache, OPTS) : target;
  }

  for (let i = 0; i < sources.length; i++) {
    const src = sources[i];
    if (!isObjectOrArray(src)) continue;
    acc = mergeAt(acc, src, 0, []);
  }
  return acc;

  // Main merge at a path
  function mergeAt(a, b, depth, path) {
    // maxDepth: directly take right value
    if (depth >= OPTS.maxDepth) {
      return cloneMaybe(b);
    }

    // If customMerge returns a value, use it
    if (typeof OPTS.customMerge === 'function') {
      const custom = OPTS.customMerge(path, a, b);
      if (custom !== undefined) {
        return OPTS.clone ? cloneDeep(custom, cloneCache, OPTS) : custom;
      }
    }

    // PreferDefined for primitive or undefined values
    if (!isObjectOrArray(a) && !isObjectOrArray(b)) {
      if (OPTS.preferDefined && b === undefined) {
        return cloneMaybe(a);
      }
      return cloneMaybe(b);
    }

    // Type resolution
    const typeA = getKind(a);
    const typeB = getKind(b);

    // If one side undefined/null
    if (b === undefined) {
      return cloneMaybe(a);
    }
    if (a === undefined) {
      return cloneMaybe(b);
    }

    // Different kinds: take right (with cloneMaybe)
    if (typeA !== typeB) {
      return cloneMaybe(b);
    }

    // Same kinds: apply specific merging
    switch (typeA) {
      case 'array':
        return mergeArrays(a, b, depth, path);

      case 'object':
        return mergeObjects(a, b, depth, path);

      case 'date': {
        const newer = (a.getTime() >= b.getTime()) ? a : b;
        return cloneDate(newer);
      }

      case 'regexp':
        return cloneRegExp(b);

      case 'map':
        return mergeMaps(a, b, depth, path);

      case 'set':
        return mergeSets(a, b, depth, path);

      case 'typedarray':
        // For typedarrays, prefer right
        return cloneTypedArray(b);

      case 'dataview':
        return cloneDataView(b);

      case 'arraybuffer':
        return cloneArrayBuffer(b);

      default:
        // Fallback: prefer right
        return cloneMaybe(b);
    }
  }

  // Object merging with key order preservation and pollution skip
  function mergeObjects(a, b, depth, path) {
    // Structural sharing when clone=false and nothing changes
    let changed = OPTS.clone; // If clone=true, we must create new references
    // Create result with plain object prototype
    const res = {};
    // Create caches for cycles: map both a and b to res
    setCloneCache(a, res);
    setCloneCache(b, res);

    const keysA = Object.keys(a).filter(k => !POLLUTION_KEYS.has(k));
    const keysBAll = Object.keys(b).filter(k => !POLLUTION_KEYS.has(k));
    const keysBOnly = keysBAll.filter(k => !(k in a));

    const orderedKeys = keysA.concat(keysBOnly);

    for (const key of orderedKeys) {
      const aHas = Object.prototype.hasOwnProperty.call(a, key);
      const bHas = Object.prototype.hasOwnProperty.call(b, key);
      const aVal = aHas ? a[key] : undefined;
      const bVal = bHas ? b[key] : undefined;

      let next;
      if (!bHas) {
        next = cloneMaybe(aVal);
        if (!OPTS.clone && next !== aVal) changed = true;
      } else if (OPTS.preferDefined && bVal === undefined) {
        next = cloneMaybe(aVal);
        if (!OPTS.clone && next !== aVal) changed = true;
      } else if (isObjectOrArray(aVal) && isObjectOrArray(bVal)) {
        next = mergeAt(aVal, bVal, depth + 1, path.concat(key));
        if (!OPTS.clone && next !== aVal) changed = true;
      } else {
        next = cloneMaybe(bVal);
        if (!OPTS.clone && next !== aVal) changed = true;
      }

      res[key] = next;
      // Maintain property order implicitly by assignment sequence
    }

    // If clone=false and nothing changed, reuse a
    if (!OPTS.clone && !changed) return a;
    return res;
  }

  // Array merging strategies
  function mergeArrays(a, b, depth, path) {
    switch (OPTS.arrayMerge) {
      case 'replace': {
        const out = cloneMaybe(b);
        return out;
      }
      case 'concat': {
        if (!OPTS.clone) {
          // Structural sharing if b is empty
          if (b.length === 0) return a;
          return a.concat(b);
        } else {
          const out = new Array(a.length + b.length);
          setCloneCache(a, out); // if cycles
          let i = 0;
          for (let j = 0; j < a.length; j++, i++) {
            out[i] = cloneDeep(a[j], cloneCache, OPTS);
          }
          for (let j = 0; j < b.length; j++, i++) {
            out[i] = cloneDeep(b[j], cloneCache, OPTS);
          }
          return out;
        }
      }
      case 'uniqueByValue': {
        const seen = new Set();
        const out = [];
        const addVal = (val) => {
          if (isPrimitive(val)) {
            if (!seen.has(val)) {
              seen.add(val);
              out.push(OPTS.clone ? val : val);
            }
          } else {
            out.push(OPTS.clone ? cloneDeep(val, cloneCache, OPTS) : val);
          }
        };
        for (let i = 0; i < a.length; i++) addVal(a[i]);
        for (let i = 0; i < b.length; i++) addVal(b[i]);
        // Structural sharing if clone=false and out equals a
        if (!OPTS.clone && arraysShallowEqual(out, a)) return a;
        return out;
      }
      default:
        throw new Error(`Unknown arrayMerge option: ${OPTS.arrayMerge}`);
    }
  }

  // Map merging: left entries then right overrides
  function mergeMaps(a, b, depth, path) {
    const out = new Map();
    setCloneCache(a, out);
    setCloneCache(b, out);

    // Copy all from a
    for (const [k, v] of a) {
      out.set(k, OPTS.clone ? cloneDeep(v, cloneCache, OPTS) : v);
    }
    // Override/add from b
    for (const [k, v] of b) {
      out.set(k, OPTS.clone ? cloneDeep(v, cloneCache, OPTS) : v);
    }

    // Structural sharing: if clone=false and mapsEqual -> reuse a
    if (!OPTS.clone && mapsEqual(out, a)) return a;
    return out;
  }

  // Set merging: union, preserving insertion order (a then b)
  function mergeSets(a, b, depth, path) {
    const out = new Set();
    setCloneCache(a, out);
    setCloneCache(b, out);

    for (const v of a) {
      out.add(OPTS.clone ? cloneDeep(v, cloneCache, OPTS) : v);
    }
    for (const v of b) {
      // For primitives dedupe naturally, for objects identity-based
      const val = OPTS.clone ? cloneDeep(v, cloneCache, OPTS) : v;
      out.add(val);
    }

    if (!OPTS.clone && setsEqual(out, a)) return a;
    return out;
  }

  // Helpers
  function cloneMaybe(val) {
    return OPTS.clone ? cloneDeep(val, cloneCache, OPTS) : val;
  }

  function setCloneCache(src, clone) {
    if (isObjectOrArray(src) && !cloneCache.has(src)) {
      try { cloneCache.set(src, clone); } catch {}
    }
  }

  function cloneDeep(val, cache, OPTS_LOCAL) {
    if (!isObjectOrArray(val)) return val;
    if (cache.has(val)) return cache.get(val);

    const kind = getKind(val);
    switch (kind) {
      case 'array': {
        const out = new Array(val.length);
        cache.set(val, out);
        for (let i = 0; i < val.length; i++) {
          out[i] = cloneDeep(val[i], cache, OPTS_LOCAL);
        }
        return out;
      }
      case 'object': {
        const out = {};
        cache.set(val, out);
        const keys = Object.keys(val).filter(k => !POLLUTION_KEYS.has(k));
        for (const k of keys) {
          out[k] = cloneDeep(val[k], cache, OPTS_LOCAL);
        }
        return out;
      }
      case 'date':
        return cloneDate(val);
      case 'regexp':
        return cloneRegExp(val);
      case 'map': {
        const out = new Map();
        cache.set(val, out);
        for (const [k, v] of val) {
          // Do not clone keys (to preserve identity semantics)
          out.set(k, cloneDeep(v, cache, OPTS_LOCAL));
        }
        return out;
      }
      case 'set': {
        const out = new Set();
        cache.set(val, out);
        for (const v of val) {
          out.add(cloneDeep(v, cache, OPTS_LOCAL));
        }
        return out;
      }
      case 'typedarray':
        return cloneTypedArray(val);
      case 'dataview':
        return cloneDataView(val);
      case 'arraybuffer':
        return cloneArrayBuffer(val);
      default:
        // For unfamiliar objects, fallback to shallow copy of own enumerable props
        const out2 = {};
        cache.set(val, out2);
        for (const k of Object.keys(val)) {
          out2[k] = cloneDeep(val[k], cache, OPTS_LOCAL);
        }
        return out2;
    }
  }

  function getKind(x) {
    if (Array.isArray(x)) return 'array';
    if (x === null) return 'null';
    const t = typeof x;
    if (t !== 'object') return 'primitive';
    if (x instanceof Date) return 'date';
    if (x instanceof RegExp) return 'regexp';
    if (x instanceof Map) return 'map';
    if (x instanceof Set) return 'set';
    // Typed arrays
    if (ArrayBuffer.isView(x) && !(x instanceof DataView)) return 'typedarray';
    if (x instanceof DataView) return 'dataview';
    if (x instanceof ArrayBuffer) return 'arraybuffer';
    // Plain object
    if (isPlainObject(x)) return 'object';
    return 'object'; // fallback
  }

  function isPlainObject(o) {
    if (Object.prototype.toString.call(o) !== '[object Object]') return false;
    const proto = Object.getPrototypeOf(o);
    return proto === Object.prototype || proto === null;
  }

  function isObjectOrArray(x) {
    return (typeof x === 'object' && x !== null);
  }

  function isPrimitive(x) {
    return x === null || (typeof x !== 'object' && typeof x !== 'function');
  }

  function cloneDate(d) {
    return new Date(d.getTime());
  }

  function cloneRegExp(r) {
    return new RegExp(r.source, r.flags);
  }

  function cloneTypedArray(ta) {
    const C = ta.constructor;
    // slice() is supported by typed arrays and clones buffer
    try {
      return ta.slice();
    } catch {
      return new C(ta);
    }
  }

  function cloneArrayBuffer(buf) {
    try {
      return buf.slice(0);
    } catch {
      const out = new ArrayBuffer(buf.byteLength);
      new Uint8Array(out).set(new Uint8Array(buf));
      return out;
    }
  }

  function cloneDataView(dv) {
    const buf = cloneArrayBuffer(dv.buffer);
    return new DataView(buf, dv.byteOffset, dv.byteLength);
  }

  function arraysShallowEqual(a1, a2) {
    if (a1.length !== a2.length) return false;
    for (let i = 0; i < a1.length; i++) {
      if (a1[i] !== a2[i]) return false;
    }
    return true;
  }

  function mapsEqual(m1, m2) {
    if (m1.size !== m2.size) return false;
    for (const [k, v] of m1) {
      if (!m2.has(k) || m2.get(k) !== v) return false;
    }
    return true;
  }

  function setsEqual(s1, s2) {
    if (s1.size !== s2.size) return false;
    for (const v of s1) {
      if (!s2.has(v)) return false;
    }
    return true;
  }
}

使用示例:

  • 合并配置并去重数组原始值: const result = deepMergeSmart( { env: 'prod', flags: ['a', 'b'], nested: { x: 1 } }, [{ env: undefined, flags: ['b', 'c'], nested: { x: 2 } }], { arrayMerge: 'uniqueByValue', preferDefined: true } );

  • 限制最大深度并自定义某路径: const result = deepMergeSmart( { theme: { colors: { primary: '#000' } } }, [{ theme: { colors: { primary: '#333' }, radius: 4 } }], { maxDepth: 2, customMerge: (path, a, b) => { // 仅对 ['theme','radius'] 应用自定义策略 if (path.length === 2 && path[0] === 'theme' && path[1] === 'radius') { return Math.max(a ?? 0, b ?? 0); } return undefined; } } );

注意:

  • clone=true(默认)时返回的结构与输入完全独立;sources 为空时等价于深拷贝 target。
  • clone=false 时会尽量复用未改变的子结构以提升性能,但返回的根节点是新或旧引用取决于是否有改动;如需严格的“完全独立”请保持 clone=true。

以下是一个健壮、可维护的 JavaScript 实现,满足你的全部要求。它提供 throttle、debounce、hybrid 三种模式,支持 leading/trailing、maxWait、raf/setTimeout 调度器,提供 cancel/flush/pendingCount,并保证 this/参数传递正确、避免重入与并发竞态。

函数名称:smartThrottleDebounce

代码: function smartThrottleDebounce( fn, waitMs, mode = 'debounce', leading = false, trailing = true, maxWaitMs, scheduler = 'setTimeout', context ) { if (typeof fn !== 'function') { throw new TypeError('fn must be a function'); } waitMs = Math.max(0, Number(waitMs) || 0); const useRaf = scheduler === 'raf' && typeof requestAnimationFrame === 'function'; const now = typeof performance !== 'undefined' && performance.now ? () => performance.now() : () => Date.now();

// Internal state let lastArgs = null; let lastThis = null; let lastResult; let lastCallTime = 0; let lastExecTime = null; // timestamp of last actual execution let burstStartTime = null; // first call time in the current burst (for maxWait) let inFlight = false; // fn is executing (async or sync) let flushRequested = false; // request immediate execution after in-flight completes let cancelled = false;

// Due times for scheduled execution let debounceDue = null; // for debounce/hybrid final trailing (after inactivity) let throttleDue = null; // for throttle end-of-window trailing let forceDue = null; // forced execution due to maxWait // Timer handle let timerActive = false; let timerId = null; // setTimeout handle or raf id

// Coalesced waiters for scheduled results const waiters = []; // array of {resolve, reject} // Pending count = waiters.length const pendingCount = () => waiters.length;

function clearTimer() { if (!timerActive) return; if (useRaf) { cancelAnimationFrame(timerId); } else { clearTimeout(timerId); } timerId = null; timerActive = false; }

function nextDue() { let d = null; if (debounceDue != null) d = debounceDue; if (throttleDue != null) d = d == null ? throttleDue : Math.min(d, throttleDue); if (forceDue != null) d = d == null ? forceDue : Math.min(d, forceDue); return d; }

function scheduleTimer() { const due = nextDue(); if (due == null) { clearTimer(); return; } const t = now(); const delay = Math.max(0, due - t); clearTimer(); timerActive = true;

if (useRaf) {
  const target = t + delay;
  const loop = () => {
    const n = now();
    if (n >= due) {
      timerActive = false;
      timerId = null;
      onTimer();
    } else {
      timerId = requestAnimationFrame(loop);
    }
  };
  timerId = requestAnimationFrame(loop);
} else {
  timerId = setTimeout(() => {
    timerActive = false;
    timerId = null;
    onTimer();
  }, delay);
}

}

function updateForceDue(currentTime) { if (typeof maxWaitMs === 'number' && maxWaitMs > 0) { const base = lastExecTime != null ? lastExecTime : (burstStartTime != null ? burstStartTime : currentTime); forceDue = base + maxWaitMs; } else { forceDue = null; } }

function resolveWaitersWith(value) { const list = waiters.splice(0, waiters.length); for (const w of list) w.resolve(value); }

function rejectWaitersWith(err) { const list = waiters.splice(0, waiters.length); for (const w of list) w.reject(err); }

function postFlight() { // After an execution finishes, run a requested flush immediately if (flushRequested) { flushRequested = false; // Ignore windows: run ASAP with latest args (if any) if (lastArgs != null && !inFlight) { // Avoid recursion: schedule microtask-like immediate run if (useRaf) { requestAnimationFrame(() => { if (!inFlight && lastArgs != null) runNow(); }); } else { setTimeout(() => { if (!inFlight && lastArgs != null) runNow(); }, 0); } } } }

function runNow() { if (inFlight) { // If currently in flight, defer immediate execution when it finishes flushRequested = true; return; } cancelled = false; const thisArg = context !== undefined ? context : lastThis; const args = lastArgs; // Snapshot args to avoid mutation during execution const snapshotArgs = Array.isArray(args) ? args.slice() : args;

inFlight = true;
try {
  const result = fn.apply(thisArg, snapshotArgs);
  if (result && typeof result.then === 'function') {
    return Promise.resolve(result)
      .then(res => {
        lastResult = res;
        inFlight = false;
        lastExecTime = now();
        resolveWaitersWith(res);
        // Reset pending windows on success if nothing more pending
        postFlight();
        scheduleTimer(); // reschedule if some due remains
        return res;
      })
      .catch(err => {
        inFlight = false;
        rejectWaitersWith(err);
        scheduleTimer();
        throw err;
      });
  } else {
    lastResult = result;
    inFlight = false;
    lastExecTime = now();
    resolveWaitersWith(result);
    postFlight();
    scheduleTimer();
    return result;
  }
} catch (err) {
  inFlight = false;
  rejectWaitersWith(err);
  scheduleTimer();
  throw err;
}

}

function onTimer() { const t = now(); let shouldRun = false;

// Forced execution (maxWait) has highest priority if there is something pending
if (forceDue != null && t >= forceDue && (waiters.length > 0 || debounceDue != null || throttleDue != null)) {
  forceDue = null;
  shouldRun = true;
}

// Throttle trailing (end-of-window)
if (!shouldRun && throttleDue != null && t >= throttleDue) {
  throttleDue = null;
  shouldRun = true;
}

// Debounce trailing (after inactivity)
if (!shouldRun && debounceDue != null && t >= debounceDue) {
  // For hybrid/debounce: ensure inactivity
  if (t - lastCallTime >= waitMs) {
    debounceDue = null;
    shouldRun = true;
  } else {
    // Not inactive yet; push next due to lastCallTime + waitMs
    debounceDue = lastCallTime + waitMs;
  }
}

if (shouldRun) {
  runNow();
}

scheduleTimer();

}

// Decide scheduling/execution for an invocation function planForInvoke(currentTime) { let ran = false; let result;

const sinceExec = lastExecTime == null ? Infinity : (currentTime - lastExecTime);

if (mode === 'debounce') {
  // leading immediate if no pending timer and no in-flight execution
  if (leading && !timerActive && !inFlight && (lastExecTime == null || currentTime - lastCallTime >= waitMs)) {
    ran = true;
    result = runNow();
  }
  // Always schedule debounce trailing when calls happen
  debounceDue = currentTime + waitMs;
  updateForceDue(currentTime);
  scheduleTimer();
  return { ran, result, scheduled: !ran };
}

if (mode === 'throttle') {
  if (sinceExec >= waitMs) {
    if (leading && !inFlight) {
      ran = true;
      result = runNow();
    } else if (trailing) {
      throttleDue = currentTime + waitMs; // first call if leading false
    }
  } else {
    if (trailing) {
      const due = (lastExecTime || currentTime) + waitMs;
      throttleDue = throttleDue == null ? due : Math.min(throttleDue, due);
    }
  }
  updateForceDue(currentTime);
  scheduleTimer();
  const scheduled = !ran && (throttleDue != null || forceDue != null);
  return { ran, result, scheduled };
}

// hybrid: throttle while active, plus a final debounce after inactivity
if (mode === 'hybrid') {
  if (sinceExec >= waitMs) {
    if (leading && !inFlight) {
      ran = true;
      result = runNow();
    } else if (trailing) {
      throttleDue = currentTime + waitMs;
    }
  } else {
    if (trailing) {
      const due = (lastExecTime || currentTime) + waitMs;
      throttleDue = throttleDue == null ? due : Math.min(throttleDue, due);
    }
  }
  // Always maintain a debounce due for final trailing after inactivity
  debounceDue = currentTime + waitMs;

  updateForceDue(currentTime);
  scheduleTimer();
  const scheduled = !ran && (debounceDue != null || throttleDue != null || forceDue != null);
  return { ran, result, scheduled };
}

// Unknown mode fallback: act as debounce
debounceDue = currentTime + waitMs;
updateForceDue(currentTime);
scheduleTimer();
return { ran: false, result: undefined, scheduled: true };

}

// Public API function invoke(...args) { const t = now(); if (burstStartTime == null) burstStartTime = t; lastArgs = args; lastThis = this; lastCallTime = t;

const plan = planForInvoke(t);

if (plan.ran) {
  return plan.result; // could be Promise or value
}

if (plan.scheduled) {
  // Return a coalesced promise to the eventual result
  return new Promise((resolve, reject) => {
    waiters.push({ resolve, reject });
  });
}

// No action (e.g., throttle with leading true, trailing false within window)
// Return the last known result for consistency
return lastResult;

}

function cancel() { clearTimer(); debounceDue = null; throttleDue = null; forceDue = null; burstStartTime = null; cancelled = true; const err = new Error('Cancelled'); rejectWaitersWith(err); }

function flush() { // "立即以最新参数触发一次":忽略窗口限制,但避免重入 if (lastArgs == null) return lastResult; if (!inFlight) { clearTimer(); debounceDue = null; throttleDue = null; forceDue = null; return runNow(); } // In-flight: queue to run immediately after current execution flushRequested = true; return new Promise((resolve, reject) => { waiters.push({ resolve, reject }); }); }

return { invoke, cancel, flush, pendingCount }; }

使用说明与要点:

  • 参数:
    • fn: 要包装的函数(可同步或返回 Promise)
    • waitMs: 窗口时长,单位毫秒
    • mode: 'throttle' | 'debounce' | 'hybrid'(默认 'debounce')
    • leading: 布尔,是否在窗口开始执行(默认 false)
    • trailing: 布尔,是否在窗口结束/停止后执行(默认 true)
    • maxWaitMs: 可选,强制执行的最长等待时间,避免持续抖动导致永不执行
    • scheduler: 'setTimeout' | 'raf'(默认 'setTimeout')。'raf' 适合动画/滚动场景(若环境不支持,则自动退回 setTimeout)
    • context: 可选,用于绑定 this(若不传,默认使用 invoke 时的 this)
  • 返回:
    • invoke(...args): 按模式控制执行,返回 Promise 或同步值(当立即执行时);当被合并延迟时返回 Promise
    • cancel(): 取消所有挂起的执行,并拒绝所有待决的 Promise
    • flush(): 立即以最新参数触发一次(忽略窗口限制),但避免与正在执行的 fn 并发;若 fn 正在执行,返回一个 Promise,在执行完成后立即再次触发
    • pendingCount(): 返回当前被合并的待决 Promise 数量,用于监控高频交互

行为细节:

  • debounce:连续触发只在停止后 waitMs 才执行;支持 leading 在首次触发立即执行;trailing 控制停止后的收尾执行;maxWaitMs 保证即便持续触发也会周期性执行。
  • throttle:在任意 waitMs 窗口内最多执行一次;leading 决定窗口开始是否立即执行;trailing 决定窗口末尾是否补一次;maxWaitMs 保证长时间内至少执行一次。
  • hybrid:持续触发按 throttle 限制执行频率,并在停止触发后进行一次 debounce 收尾。
  • Promise 统一返回:对延迟执行的调用,invoke 返回一个 Promise,完成后统一把最新一次执行结果分发给所有等待者。
  • 竞态与重入:内部通过 inFlight 和 flushRequested 控制,避免并发重入;flush 会在当前执行完成后立即再次执行一次。
  • this 与参数:执行时使用 context(若提供)或 invoke 的 this,参数为最后一次调用的最新参数。
  • raf 调度:通过 requestAnimationFrame 循环等待到期,适合动画/滚动场景;否则用 setTimeout。

如需 TypeScript 声明或更强类型约束,我可以补充类型定义与更细致的单元测试示例。

示例详情

该提示词已被收录:
“程序员必备:提升开发效率的专业AI提示词合集”
让 AI 成为你的第二双手,从代码生成到测试文档全部搞定,节省 80% 开发时间
√ 立即可用 · 零学习成本
√ 参数化批量生成
√ 专业提示词工程师打磨

解决的问题

提供一个高效、专业的工具,帮助开发者快速生成满足复杂逻辑和约束条件的高质量代码函数,提升开发效率并减少潜在错误。

适用用户

后端开发工程师

需要快速实现复杂功能逻辑的高效函数,同时确保代码性能与边界条件的可靠处理。

算法工程师

处理算法优化时,用于生成逻辑严谨且高效的代码实现,节省繁杂的代码编写时间。

编程初学者

借助模板化的代码输出,学习编程语言核心函数结构和常见实践技巧,提升编程能力。

特征总结

智能生成高效函数:快速生成满足特定参数和逻辑需求的高质量函数,简化开发流程。
自动处理边界情况:通过添加约束条件,确保函数能够轻松应对异常场景,提升可靠性。
覆盖多种编程语言:支持定制化选择不同语言,解决多技术栈项目中的开发需求。
逻辑严谨更优化:基于专家级逻辑设计,确保生成的函数在性能和可维护性上表现出色。
灵活支持算法设计:结合需求推荐合适算法或使用特定库,优化实现效果。
一键定制复杂逻辑:轻松快速处理复杂逻辑需求,节省繁杂的编码时间。
支持团队协作需求:为开发团队成员提供一致的标准化输出,减少沟通成本。
提升学习与培训效果:作为学习工具,为新手提供清晰的代码结构与实践指导。
加速原型开发:通过快速生成可用代码,加快产品迭代效率。

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

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

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

2. 发布为 API 接口调用

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

3. 在 MCP Client 中配置使用

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

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

您购买后可以获得什么

获得完整提示词模板
- 共 111 tokens
- 4 个可调节参数
{ 函数名称 } { 参数列表 } { 逻辑描述 } { 返回值说明 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
使用提示词兑换券,低至 ¥ 9.9
了解兑换券 →
限时半价

不要错过!

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

17
:
23
小时
:
59
分钟
:
59