普通视图

发现新文章,点击刷新页面。
昨天 — 2025年9月6日首页

🚀Map的20个神操作,90%的开发者浪费了它的潜力!最后的致命缺陷让你少熬3天夜!

作者 子兮曰
2025年9月6日 15:24

开篇

“还在用Object当字典?Map的隐藏技能让你代码效率飙升300%!但用错最后的致命缺陷,小心项目崩盘!”
作为前端开发者,你是否遇到过这些问题?

  • 键名只能用字符串,处理复杂数据结构束手无策
  • 遍历时顺序混乱,需要手动排序
  • 内存泄漏频发,却找不到原因
    今天,我用20个硬核技巧+真实场景代码,彻底榨干Map的每一滴性能!

与Object的抉择

场景 Map✅ Object❌
键类型 任意类型 仅字符串/Symbol
顺序保证 插入顺序 不可预测
性能 频繁增删快30% 读取略快

1️⃣ 类型自由键名

// 用对象作为键!Object做不到
const userPermissions = new Map();
const adminUser = { id: "U001", role: "admin" };

userPermissions.set(adminUser, ["delete", "edit"]);
console.log(userPermissions.get(adminUser)); // ["delete", "edit"]

2️⃣ LRU缓存淘汰算法

class LRUCache {
  constructor(capacity) {
    this.cache = new Map();
    this.capacity = capacity;
  }

  get(key) {
    if (!this.cache.has(key)) return -1;
    const value = this.cache.get(key);
    this.cache.delete(key); // 删除后重新插入保证顺序
    this.cache.set(key, value);
    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) this.cache.delete(key);
    if (this.cache.size >= this.capacity) {
      // 淘汰最久未使用的键(Map首个元素)
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }
    this.cache.set(key, value);
  }
}

3️⃣ 深度克隆利器

function deepClone(obj, map = new Map()) {
  if (map.has(obj)) return map.get(obj); // 循环引用处理

  const clone = Array.isArray(obj) ? [] : {};
  map.set(obj, clone);

  for (let key in obj) {
    clone[key] = typeof obj[key] === "object" ? 
      deepClone(obj[key], map) : obj[key];
  }
  return clone;
}

4️⃣ DOM事件管理器

// 避免内存泄漏:WeakMap自动回收
const eventMap = new WeakMap();

function addEvent(element, event, handler) {
  if (!eventMap.has(element)) {
    eventMap.set(element, new Map());
  }
  eventMap.get(element).set(event, handler);
  element.addEventListener(event, handler);
}

// 移除时自动清理
element.removeEventListener(event, eventMap.get(element).get(event));

5️⃣ 高性能计数器

// 比Object快40%的计数方案
const frequencyCounter = (arr) => {
  const map = new Map();
  arr.forEach(item => {
    map.set(item, (map.get(item) || 0) + 1);
  });
  return map;
};

console.log(frequencyCounter([1,2,2,3,3,3])); 
// Map(3) {1 => 1, 2 => 2, 3 => 3}

6️⃣ 迭代器性能优化

// 避免将Map转为数组再遍历
const bigMap = new Map(/* 大量数据 */);

// 错误做法:消耗O(n)额外内存
Array.from(bigMap.keys()).forEach(key => {});

// 正确:直接使用迭代器
for (const key of bigMap.keys()) {
  // 处理每个key,内存占用O(1)
}

7️⃣ JSON转换黑科技

// Map转JSON的完整方案
const map = new Map([['name', 'John'], [1, 'one']]);

// 自定义转换函数(支持非字符串键)
function mapToJson(map) {
  return JSON.stringify(Array.from(map));
}

// 解析回Map
function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

console.log(mapToJson(map)); // [["name","John"],[1,"one"]]

8️⃣ 内存泄漏检测

// 用Map跟踪未释放资源
const resourceTracker = new Map();

function loadResource(id) {
  const resource = fetchResource(id); // 模拟资源加载
  resourceTracker.set(id, resource);
  return resource;
}

// 定期检查未释放资源
setInterval(() => {
  resourceTracker.forEach((res, id) => {
    console.warn(`资源 ${id} 未释放!`);
  });
}, 60_000);

9️⃣ 树形结构扁平化

// 使用Map快速扁平化树形数据
function flattenTree(root, key = 'id') {
  const nodeMap = new Map();
  
  function traverse(node) {
    nodeMap.set(node[key], node);
    node.children?.forEach(traverse);
  }
  
  traverse(root);
  return nodeMap;
}

// 示例:通过id直接访问任意节点
const tree = { id: 1, children: [ {id: 2}, {id: 3} ] };
const flatMap = flattenTree(tree);
console.log(flatMap.get(2)); // {id: 2}

🔟 双向映射

// 实现键值双向查找
class BiMap {
  constructor() {
    this.keyToValue = new Map();
    this.valueToKey = new Map();
  }

  set(key, value) {
    this.keyToValue.set(key, value);
    this.valueToKey.set(value, key);
  }

  getByKey(key) { return this.keyToValue.get(key); }
  getByValue(value) { return this.valueToKey.get(value); }
}

// 使用场景:中英文双向翻译
const dict = new BiMap();
dict.set('苹果', 'apple');
dict.getByKey('苹果'); // 'apple'
dict.getByValue('apple'); // '苹果'

1️⃣1️⃣ 并发安全锁

// 用Map实现简单互斥锁
const lockMap = new Map();

async function withLock(resourceId, task) {
  // 如果已有锁,等待释放
  while (lockMap.has(resourceId)) {
    await new Promise(resolve => setTimeout(resolve, 10));
  }

  // 加锁
  lockMap.set(resourceId, true);
  try {
    return await task();
  } finally {
    // 释放锁
    lockMap.delete(resourceId);
  }
}

// 使用示例
withLock('user:1001', async () => {
  // 修改用户数据的独占操作
});

1️⃣2️⃣ 依赖关系解析

// 拓扑排序解决依赖问题
function resolveDependencies(depsMap) {
  const sorted = [];
  const inDegree = new Map();
  const graph = new Map();

  // 初始化图和入度
  for (const [node, deps] of depsMap) {
    graph.set(node, deps);
    inDegree.set(node, deps.length);
  }

  // 找到入度为0的节点
  const queue = Array.from(graph.keys()).filter(node => inDegree.get(node) === 0);

  while (queue.length) {
    const node = queue.shift();
    sorted.push(node);
    
    // 减少依赖当前节点的入度
    graph.forEach((deps, dependent) => {
      if (deps.includes(node)) {
        const newDegree = inDegree.get(dependent) - 1;
        inDegree.set(dependent, newDegree);
        if (newDegree === 0) queue.push(dependent);
      }
    });
  }

  return sorted;
}

// 示例:包依赖解析
const deps = new Map([
  ['a', ['b', 'c']],
  ['b', ['c']],
  ['c', []]
]);
console.log(resolveDependencies(deps)); // ['c', 'b', 'a'] 或 ['c', 'a', 'b'] 等合法拓扑序

1️⃣3️⃣ 多级缓存实战

class MultiLevelCache {
  constructor() {
    this.l1 = new Map(); // 内存缓存
    this.l2 = new Map(); // 持久化缓存(模拟)
  }

  async get(key) {
    // L1命中
    if (this.l1.has(key)) return this.l1.get(key);
    
    // L2命中
    if (this.l2.has(key)) {
      const value = this.l2.get(key);
      // 回填L1
      this.l1.set(key, value);
      return value;
    }

    // 缓存未命中,从数据源加载
    const data = await fetchData(key);
    this.l1.set(key, data);
    this.l2.set(key, data);
    return data;
  }
}

1️⃣4️⃣ 事件派发中心

// 基于Map的通用事件总线
class EventEmitter {
  constructor() {
    this.events = new Map();
  }

  on(event, listener) {
    const listeners = this.events.get(event) || [];
    listeners.push(listener);
    this.events.set(event, listeners);
  }

  emit(event, ...args) {
    const listeners = this.events.get(event);
    listeners?.forEach(fn => fn(...args));
  }

  off(event, listener) {
    const listeners = this.events.get(event) || [];
    this.events.set(
      event, 
      listeners.filter(fn => fn !== listener)
    );
  }
}

1️⃣5️⃣ 表单状态管理

// 用Map管理动态表单字段
class FormState {
  constructor() {
    this.fields = new Map();
  }

  addField(name, validator) {
    this.fields.set(name, {
      value: '',
      error: null,
      validator
    });
  }

  setValue(name, value) {
    const field = this.fields.get(name);
    if (!field) return;
    
    field.value = value;
    field.error = field.validator(value);
    this.fields.set(name, field);
  }

  get isValid() {
    return Array.from(this.fields.values())
      .every(field => field.error === null);
  }
}

1️⃣6️⃣ 数据变更追踪

// 使用Proxy+Map监听数据变化
const changeTracker = new Map();
const proxy = new Proxy(obj, {
  set(target, key, value) {
    changeTracker.set(key, { old: target[key], new: value });
    return Reflect.set(...arguments);
  }
});

1️⃣7️⃣ 权限位运算映射

// 将权限位映射为可读名称
const PERM_MAP = new Map([
  [1 << 0, 'READ'],
  [1 << 1, 'WRITE'],
  [1 << 2, 'EXECUTE']
]);

function decodePermissions(bits) {
  return Array.from(PERM_MAP.keys())
    .filter(perm => bits & perm)
    .map(perm => PERM_MAP.get(perm));
}

1️⃣8️⃣ 算法优化(两数之和)

// 时间复杂度O(n)的解决方案
function twoSum(nums, target) {
  const numMap = new Map();
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (numMap.has(complement)) {
      return [numMap.get(complement), i];
    }
    numMap.set(nums[i], i);
  }
}

1️⃣9️⃣ 路由匹配加速器

// 动态路由参数快速提取
const routeMap = new Map();
routeMap.set('/user/:id', params => {});

function matchRoute(path) {
  for (const [pattern, handler] of routeMap) {
    const regex = new RegExp(`^${pattern.replace(/:\w+/g, '([^/]+)')}$`);
    const match = path.match(regex);
    if (match) return handler(match.slice(1));
  }
}

2️⃣0️⃣ 跨窗口状态同步

// 使用BroadcastChannel+Map同步状态
const stateMap = new Map();
const channel = new BroadcastChannel('app_state');

channel.onmessage = (e) => {
  const { key, value } = e.data;
  stateMap.set(key, value);
};

function setGlobalState(key, value) {
  stateMap.set(key, value);
  channel.postMessage({ key, value });
}

终极建议:三大黄金法则

  1. 规模决策矩阵

    graph LR
    A[数据规模] --> B{选择方案}
    B -->| < 10项 | C[Object]
    B -->| 10-50项 | D[根据操作类型选择]
    B -->| > 50项 | E[Map]
    
  2. 内存监控策略

    // 检测Map内存占用
    const memoryBefore = performance.memory.usedJSHeapSize;
    const bigMap = new Map(/* 大数据 */);
    const memoryAfter = performance.memory.usedJSHeapSize;
    console.log(`Map内存消耗:${(memoryAfter - memoryBefore) / 1024} KB`);
    
  3. 类型转换对照表

    转换目标 方案 注意事项
    Object Object.fromEntries(map) 丢失非字符串键
    Array [...map] 保留键值对结构
    JSON 自定义序列化 需处理循环引用

不足与解决方案补遗

⚠️致命缺陷1:遍历中断问题

// 遍历中删除会引发异常
const map = new Map([['a', 1], ['b', 2]]);

// 错误!导致迭代器失效
for (const key of map.keys()) {
  if (key === 'a') map.delete('b');
}

// 正确:先收集要删除的键
const toDelete = [];
for (const [key] of map) {
  if (key.startsWith('temp_')) toDelete.push(key);
}
toDelete.forEach(k => map.delete(k));

⚠️致命缺陷2:无法直接响应式

// Vue3中需要手动触发更新
import { reactive } from 'vue';

const state = reactive({
  data: new Map() // 不会自动触发渲染!
});

// 解决方案:使用自定义ref
function reactiveMap(initial) {
  const map = new Map(initial);
  return {
    get: key => map.get(key),
    set: (key, value) => {
      map.set(key, value);
      triggerRef(); // 手动触发更新
    }
  };
}

⚠️致命缺陷3:JSON序列化黑洞

const map = new Map([["name", "Vue"], ["ver", 3]]);
JSON.stringify(map); // 输出 "{}" !!

// 解决方案:自定义转换器
const mapToObj = map => Object.fromEntries(map);
JSON.stringify(mapToObj(map)); // {"name":"Vue","ver":3}

⚠️致命缺陷4:内存占用高出Object 20%

  • 小型键值对(<10个)用Object更划算
  • 超过50个键值对时Map优势明显

⚠️致命缺陷5:遍历陷阱

// 错误!每次size计算消耗O(n)
for(let i=0; i<map.size; i++) { ... }

// 正确!迭代器直接访问
for(const [key, value] of map) { ... }

⚠️致命缺陷6:键选择原则

  • 对象键:用WeakMap防内存泄漏
  • 基础类型:普通Map更高效

结语

"掌握这20招,你将成为团队中的Map宗师!但切记:没有银弹,在小型配置项中Object仍是首选。真正的技术高手,懂得在合适场景选用合适工具。尤其LRU缓存和WeakMap防泄漏,下次性能优化至少省你3天熬夜时间!”

昨天以前首页

如何错误手写 ES2025 新增的 Promise.try() 静态方法

2025年9月4日 22:48

全文速览

欢迎关注 前端情报社。大家好,我是社长林语冰。

Promise 从 ES2015 成为 JavaScript 的一部分。10 年后,ES2025 是第 16 版 JavaScript 语言规范,它新增了 9 种颠覆性功能,Promise.try() 就是其中之一。

Promise.try() 提案并非原创,ES2025 之前,bluebird 和 p-try 等流行库就提供了等价的功能。但我发现 GitHub 上一些遗留源码,为了不使用第三方库,自己会尝试手写模拟实现 Promise.try() 的功能,但部分实现采用了下列错误方案:

  • Promise.resolve() 会导致异常逃逸
  • Promise.prototype.then() 会产生多余的微任务

本文我们会探讨 ES2025 最新 Promise.try() 静态方法的基本用法,以及如何正确手写 Promise.try()

使用场景

Promise.try() 适用于将回调封装为 Promise 风格,然后安全开启链式调用的场景:

import { readFile } from 'node:fs/promises'

function readLocalFile(path) {
  if (!path) {
    throw new Error('path 不能为空')
  }

  path = new URL(path, import.meta.url)

  return readFile(path, { encoding: 'utf8' })
}

Promise.try(readLocalFile).catch(console.log) // ❌ path 不能为空
Promise.try(readLocalFile, './package.json').then(console.log) // ✅

这里,Promise.try() 会接受一个回调,并将返回值转化为 promise,以便后续开启链式调用。此外,回调内部的同步/异步异常,都会被捕获并转化为失败的 promise 实例。

只有正确掌握 Promise.try() 的行为机制,我们才能正确手写模拟 Promise.try()

异常逃逸

GitHub 上一些遗留代码采用了 ES6 的 Promise.resolve() 来模拟 Promise.try() 的行为,其实是一种错误的方案:

Promise.try = function promiseTry(fn, ...args) {
  return Promise.resolve(fn(...args))
}

Promise.try(readLocalFile, './package.json').then(console.log) // ✅
Promise.try(readLocalFile).catch(console.log) // ❌ 报错,异常逃逸

这里,Promise.resolve() 虽然能将回调的返回值封装为 promise 实例,但它无法捕获回调内部的同步异常。所以,同步异常会逃逸,最终导致程序执行终端并报错。

同理,采用 ES2024 新增的 Promise.withResolvers() 方法,也会导致异常逃逸:

Promise.try = function promiseTry(fn, ...args) {
  let { promise, resolve } = Promise.withResolvers()
  resolve(fn(...args))
  return promise
}

Promise.try(readLocalFile, './package.json').then(console.log) // ✅
Promise.try(readLocalFile).catch(console.log) // ❌ 报错,异常逃逸

如果你非要用上述两种 API 来模拟实现 Promise.try(),那只能手动 try/catch 处理同步异常,并转化为失败的 promise。

以 ES2024 的 Promise.withResolvers() 为例:

Promise.try = function promiseTry(fn, ...args) {
  let { promise, resolve, reject } = Promise.withResolvers()
  try {
    resolve(fn(...args))
  } catch (e) {
    reject(e)
  }
  return promise
}

这种方案允许我们捕获同步异常,并转化为失败的 promise,但混用了同/异步的异常处理方式,比超人内裤外穿还碍眼。

丢失同步行为

为了利用 Promise 自动捕获同步异常的机制,有人采用了 then() 方法来包裹:

Promise.try = function promiseTry(fn, ...args) {
  return Promise.resolve().then(() => fn(...args))
}

Promise.try(readLocalFile).catch(console.log) // ❌ path 不能为空
Promise.try(readLocalFile, './package.json').then(console.log) // ✅

这里,我们利用了 then() 方法的底层机制,其内部会自动捕获异常,并转化为失败的 promise,不需要我们手动 try/catch

可以看到,在这种场景下,我们得到了和原生 Promise.try() 一致的结果。bug 在于,fn() 函数不要求一定是异步函数,它可能是一个同步执行的回调,但我们将其放在 then() 方法中,它被强制转化为一个永远只能异步执行的微任务。

热补丁

不同于 then()new Promise()executor() 是同步调用的 阻塞型回调

console.log('sync:', 1)

function maybeSync() {
  console.log('maybeSync:', 2)
  throw new Error('同步异常')
}

new Promise(function executor(resolve) {
  resolve(maybeSync())
})
  .then(() => {
    console.log('async:', 3)
  })
  .catch((e) => {
    console.log(`catch ${e}:`, 4)
  })

console.log('sync:', 5)
/**
 * sync: 1
 * maybeSync: 2
 * sync: 5
 * catch 同步异常: 4
 */

因此,ES2025 之前,采用 new Promise() 模拟 Promise.try() 是一种可行的 热补丁

Promise.try = function promiseTry(fn, ...args) {
  return new Promise((resolve, reject) => {
    try {
      resolve(fn(...args))
    } catch (e) {
      reject(e)
    }
  })
}

这里,new Promise() 内部调用回调,同时将返回值封装为一个 promise 实例。

由于 ES6 标准的 Promise 构造函数内部会自动捕获异常,并转化为失败的 promise 实例,所以上述代码可以优化为:

Promise.try = function (f, ...args) {
  return new Promise((resolve) => {
    resolve(f(...args))
  })
}

两种实现方式的功能是等价的,只是后者更加精简。

此外,async function 也能模拟 Promise.try()

Promise.try = async function (f, ...args) {
  return f(...args)
}

那为何还需要 Promise.try()

async function 初学者会误解其内代码都异步执行,其实没有 awaitasync function 会同步执行。同理,它们会误解 async function 始终返回成功的 promise,除非函数体中存在 try/catch

Promise.try() 更直观,能减少初学者的认知负荷。TC39 委员如是说,“Promise API 和 async/await 语法应互补实现等价功能,Promise.try() 是缺失的拼图。async/await 语法无法取代 Promise API,让它们并行不悖至关重要。”

高潮总结

根据《ecmascript 语言规范》,ES2025 新增 promise.try() 静态方法,用于调用可能返回 promise 的回调,最终返回 promise。

实际开发中,部分用户为了不安装第三方模块,会手动模拟实现 Promise.try() 方法,在不兼容的平台中使用这种现代 API。然而,部分实现采用了 Promise.resolve()then() 方法错误实现,会不小心引入 bug。

推荐采用原生 Promise.try(),或集成 polyfill 扩展来重构代码屎山,消除技术负债。如果要手写 Promise.try(),请使用 new Promise() 的方案。

Promise 再次进化,ES2025 新增 Promise.try() 静态方法

2025年9月4日 22:46

全文速览

欢迎关注 前端情报社。大家好,我是社长林语冰。

Promise 从 ES2015 成为 JavaScript 的一部分。10 年后,ES2025 是第 16 版 JavaScript 语言规范,它新增了 9 种颠覆性功能,Promise.try() 就是其中之一。

顾名思义,Promise.try()Promise 类新增了一个静态方法,它接收一个 行为不可知的阻塞型回调

  • 它可能是异步函数;
  • 它可能返回 promise;
  • 它可能引发异常
  • .....

然后 立即调用 该回调,最终返回一个 promise。

本文我们会探讨 ES2025 最新 Promise.try() 静态方法的基本用法,高级用例,底层原理和编程技巧。

ES2025 Promise.try()

Promise.try() 提案并非原创,ES2025 之前,bluebird 和 p-try 等流行库就提供了等价的功能。

bluebird 官方文档提供了基本示例:

function getUserById(id) {
  return Promise.try(function () {
    if (typeof id !== 'number') {
      throw new Error('id 要求为数字!')
    }
    return db.getUserById(id)
  })
}

getUserById().catch(console.log)
// Error: id 要求为数字!

现实开发中的代码往往错综复杂,有的业务逻辑可能混用同步/异步操作。上述代码中,输入验证是同步错误,数据库操作可能是异步操作。Promise.try() 可以用于封装这些复杂业务,确保无论回调是否异步执行或报错,都能返回一个 promise,继续链式调用。

这就是 ES2025 Promise.try() 的用途,但我们不需要再安装 bluebird 等第三方库。

具体而言,Promise.try() 接受一个行为不可知的 阻塞型回调 并立即调用它,最终返回 promise:

// ES2025 之后的写法:
// ✅️ 1. 回调返回非 promise
Promise.try(() => '同步结果').then(console.log)

// ✅️ 2. 回调同步报错
Promise.try(() => {
  throw new Error('同步异常')
}).catch(console.log)

// ✅️ 3. 回调返回成功的 promise
Promise.try(() => Promise.resolve('fulfillment')).then(console.log)

// ✅️ 4. 回调返回失败的 promise
Promise.try(() => Promise.reject('rejection')).catch(console.log)

// ✅️ 5. 回调是正常执行的异步函数
Promise.try(async () => {
  let data = await Promise.resolve('异步结果')
  // 其他业务......
  return data
}).then(console.log)

// ✅️ 6. 回调是异步报错的异步函数
Promise.try(async () => {
  try {
    let result = await Promise.reject('异步异常')
  } catch (e) {
    throw e
  }
}).catch(console.log)

可以看到,Promise.try() 是一个更加强大和通用的现代原生 API,适用于各种复杂的回调场景。

另请参考,其 TypeScript 源码的函数签名如下:

interface PromiseConstructor {
  try<T, U extends unknown[]>(
    callbackFn: (...args: U) => T | PromiseLike<T>,
    ...args: U
  ): Promise<Awaited<T>>
}

底层原理

ES2025 之前,想要实现 Promise.try() 的等价功能,除了引入第三方模块,还可以使用 new Promise() 手动封装,只要你懂得基本的底层原理。

具体而言,new Promise() 模拟 Promise.try() 的底层原理如下:

Promise.try = function (f, ...args) {
  return new Promise((resolve) => {
    resolve(f(...args))
  })
}

这里,new Promise() 内部调用回调,同时将返回值封装为一个 promise 实例。

比起安装第三方模块或手动封装,ES2025 原生的 Promise.try() 显然更符合人体工程学。

不同于 new Promise(resolve => resolve(f())) 这种遗臭万年的代码屎山,Promise.try(f) 是一种更精简的“代码高尔夫”:你能用 更少的字符 重构等价的功能。

薛定谔的异步

现实开发中,某些 API 的回调可能同步/异步执行:

let map = new Map([[1, 'cache']])

function log(data) {
  console.log(`callback: ${data}`)
}

function zalgoAPI(id, cb) {
  if (map.has(id)) {
    // 若缓存命中,则回调同步执行
    cb(map.get(id))
  } else {
    // 若缓存未命中,则回调异步执行
    setTimeout(() => {
      map.set(id, 'update data')
      cb(map.get(id))
    }, 1_000)
  }
}

console.log('sync:', 1)
zalgoAPI(1, log)
zalgoAPI(2, log)
console.log('sync:', 2)
/**
 * sync: 1
 * callback: cache
 * sync: 2
 * callback: update data
 */

“npm 之父”将这种难以预测的设计屎山称为 Zalgo 问题(混沌问题)。

为了解决 Zalgo 问题,我们可以使用 Promise.try() 简单重构,确保回调始终异步执行:

import { setTimeout as setTimeoutPromise } from 'node:timers/promises'

let map = new Map([[1, 'async cache']])

function asyncAPI(id) {
  return Promise.try(() => {
    if (map.has(id)) {
      return map.get(id)
    } else {
      return setTimeoutPromise(1_000).then(() => {
        map.set(id, 'async data')
        return map.get(id)
      })
    }
  })
}

console.log('sync:', 1)
asyncAPI(1).then(log)
asyncAPI(2).then(log)
console.log('sync:', 2)
/**
 * sync: 1
 * sync: 2
 * callback: cache
 * callback: update data
 */

实用技巧

此外,类似 setTimeout()Promise.try() 支持 实参转发

setTimeout(function closure() {
  console.log('ES2025')
}, 1_000)

// 👇️ 实参转发
setTimeout(console.log, 1_000, 'ES2025')

// ********************************

Promise.try(function closure() {
  console.log('ES2025')
})

// 👇️ 实参转发
Promise.try(console.log, 'ES2025')

两种写法功能等价,但后者减少了冗余闭包,性能更棒。

浏览器兼容性

2025 年 1 月,Promise.try() 成为 Baseline 基准可用 新特性,所有最新主流浏览器都原生支持。

在尚不支持 Promise.try() 的旧平台中,可以按需引入 polyfill(功能补丁) 优雅降级

以 GitHub 人气最高的 core-js 为例,先用 npm / pnpm 安装 core-js 模块:

npm install core-js@latest
# 或者:
pnpm install core-js@latest

然后导入开箱即用的 polyfill,更多细节请参考 core-js 官方文档:

// 集成 polyfill
import 'core-js/es/promise/try.js'

// 基本用法
Promise.try(console.log, 'Hello ES2025')

高潮总结

根据《ECMAScript 语言规范》,es2025 新增 Promise.try() 静态方法,用于调用可能返回 promise 的回调,最终返回 promise。

作为一个更通用的现代 API 糖Promise.try() 无差别执行开发者提供的回调,高效且稳健地开启 Promise 链,更符合人体工程学。

有了 Promise.try(),库作者免于反复造轮子手写样板代码,也避免了错误模拟 Promise.try() 引入潜在 bug,更避免了集成 bluebird 等第三方库增加打包体积。

推荐采用原生 Promise.try(),或集成 polyfill 扩展来重构代码屎山,消除技术负债。

❌
❌