热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
本提示词以专家视角生成高质量函数,支持指定参数、逻辑和返回值,自动考虑边界情况,并可选使用库函数或最佳实践,提升代码可维护性、可读性和执行效率,适用于前端及通用开发场景。
下面提供一个可直接使用的安全请求函数实现,包含内存缓存、超时与重试(指数/线性回退+抖动)、智能解析 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;
};
说明与要点:
${METHOD}:${url},如需更细粒度请传入 options.cache.key。下面提供一个符合要求的 JavaScript 函数 deepMergeSmart。默认不可变深度合并(clone=true),支持循环引用检测、可选路径级自定义合并、数组多策略、特殊类型处理、键顺序保持与在 clone=false 时尽量复用结构。
函数说明:
注意:
代码:
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; } } );
注意:
以下是一个健壮、可维护的 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 }; }
使用说明与要点:
行为细节:
如需 TypeScript 声明或更强类型约束,我可以补充类型定义与更细致的单元测试示例。
提供一个高效、专业的工具,帮助开发者快速生成满足复杂逻辑和约束条件的高质量代码函数,提升开发效率并减少潜在错误。
需要快速实现复杂功能逻辑的高效函数,同时确保代码性能与边界条件的可靠处理。
处理算法优化时,用于生成逻辑严谨且高效的代码实现,节省繁杂的代码编写时间。
借助模板化的代码输出,学习编程语言核心函数结构和常见实践技巧,提升编程能力。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
半价获取高级提示词-优惠即将到期